갈루아의 반서재



Getting Started with CLISP (17) - 텍스트 출력 및 읽기 Printing and Reading Text



이번 포스팅에서는 사용자 인터페이스의 가장 기본인 command-line 인터페이스에 대해서 알아본다. 


Printing and Reading Text

스크린에 프린트하기

1
2
3
4
5
6
7
 
Break 83 [84]> (print "foo")
 
"foo"
"foo"
Break 83 [84]>
 
cs

"foo"가 2번 출력이 된다. 첫번째 "foo"는 실제 print 함수가 출력하는 부분이며, 두번째 "foo" 는 REPL이 입력된 표현식의 값을 그대로 출력해주기 때문이다. 

숙련된 리스퍼들은 prin1 함수도 애용한다. print 와 prin1 의 차이점을  이해하기 위해 다음의 예를 살펴보자.

1
2
3
4
5
6
7
8
9
10
Break 84 [85]>  (progn (print "this")
(print "is")
(print "a")
(print "test"))
 
"this"
"is"
"a"
"test"
"test"
cs

print 함수는 각각의 아이템이 별도의 라인으로 출력된다. 그러면 prin1 을 보자. 

1
2
3
4
5
6
7
8
Break 86 [87]>  (progn (prin1 "this")
(prin1 "is")
(prin1 "a")
(prin1 "test"))
"this""is""a""test"
"test"
Break 86 [87]>
 
cs

보는 바와 같이 prin1 하나의 라인에 출력이 된다. 좀 더 정확히 얘기하면, print 와 prin1 명령은 모든 면에서 같지만, print 함수는 값을 출력하기 전에 새로운 라인을 생성한다는 점만 다르다. 그리고 print 함수는 출력된 값 마지막에 스페이스를 둔다. prin1이 좀 더 심플하고 유연한 함수인 관계로 리스퍼들은 주로 prin1 을 많이 사용한다. 


Saying Hello to the User

사용자의 이름을 물어보고 인사를 하는 함수를 만들어보자. 구식처럼 보여도 이름을 따옴표로 감싸는 것을 잊지말자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Break 87 [88]> (defun say-hello ()
 (print "Please type your name.")
 (let ((name (read)))
 (print "Nice to meet you, ")
 (print name)))
SAY-HELLO
Break 87 [88]> (say-hello)
 
"Please type your name." "alex"
 
"Nice to meet you, "
"alex"
"alex"
Break 87 [88]>
 
cs

먼저, 사용자에게 이름을 물어보는 메시지를 출력하고, read 함수에 의해 읽혀진 값이 셋팅되는 name 이라는 지역 변수를 정의했다. read 함수는 REPL 에서 사용자가 무언가를 타이핑하는 것을 기다린 후 사용자의 타이핑이 끝나면 변수 이름이 그 값으로 셋팅된다. 

위의 예에서 보듯이 print 와 read 함수는 여러분이 예상한대로 동작한다. 하지만 거기에는 확연히 눈에 띄는 이상한 점이 있다. 모든 표시되고 입력된 값이 따옴표에 의해 감싸진다는 것이다. 


Starting with print and read

print 와 read 함수는 값을 컴퓨터의 입장에서 생각하는 것이지 인간의 입장에서 이해하는 것은 아니다. 컴퓨터는 문자열이 따옴표에 둘러 싸이는 것을 좋아한다. 텍스트가 따옴표로 둘러싸이면 아무리 구형 컴퓨터라도 그것이 문자열임을 알아챈다. print 와 read 함수는 이런 철학이 극단까지 담겨 있는 것이다. 

다음의 코드는 앞선 코드와 유사하지만, 문자열 대신 숫자가 입력되는 부분이 다르다. 따옴표 없이 숫자를 어떻게 처리하는지 살펴보자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Break 88 [89]> (defun add-five ()
 (print "please enter a number:")
 (let ((num (read)))
 (print "When I add five I get")
 (print (+ num 5))))
ADD-FIVE
Break 88 [89]> (add-five)
 
"please enter a number:" 6
 
"When I add five I get"
11
11
Break 88 [89]>
 
cs

몇 가지 예를 더 살펴보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
Break 89 [90]> (print '3)
3
3
Break 89 [90]> (print '3.4)
 
3.4
3.4
Break 89 [90]> (print "foo")
 
"foo"
"foo"
Break 89 [90]> (print '#\a)
#\a
#\a
Break 89 [90]>
cs

심볼의 경우 모두 대문자로 표시된다. 

1
2
3
Break 90 [91]> (print 'foo)
FOO
FOO
cs

앞선 예에서는 명시적으로 각각의 값 앞에 따옴표 등을 넣었다. 만약 생략되더라도 심볼 이름을 제외하고는 무방하다. 마지막 예는 캐릭터가 리스프에서는 어떻게 입력되는지를 보여주고 있다. 리스프 캐릭터를 생성하기 위해서는 실제 캐릭터 앞에 #\ 를 집어넣으면 된다. 


Reading and Printing Stuff the Way Humans Like It

princ 함수는 인간이 선호하는 방식으로 출력을 해낸다. 다음의 예를 보자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
Break 92 [93]> (princ '3)
3
3
Break 92 [93]> (princ '3.4)
3.4
3.4
Break 92 [93]> (princ "foo")
foo
"foo"
Break 92 [93]> (princ 'foo)
FOO
FOO
Break 92 [93]> (princ '#\a)
a
#\a
Break 92 [93]>
 
cs


1
2
3
4
5
6
7
8
9
 
Break 94 [95]>  (progn (princ "This sentence will be interrupted")
(princ #\newline)
(princ "by an annoying newline character."))
This sentence will be interrupted
by an annoying newline character.
"by an annoying newline character."
Break 94 [95]>
 
cs

print 명령의 가장 큰 매력은 있는그대로 출력이 된다는 점이다. 하지만 이 얘기는 자의적인 텍스트 출력은 불가능하다는 이야기이다. 반면 princ 는 원하는대로 출력이 가능하다. 이상의 지식을 바탕으로, 처음의 say-hello 함수를 부호없이 코딩해보자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
 
Break 95 [96]> (defun say-hello ()
 (princ "Please type your name:")
 (let ((name (read-line)))
  (princ "Nice to meet you, ")
  (princ name)))
SAY-HELLO
Break 95 [96]> (say-hello)
Please type your name:Bob O'Malley
Nice to meet you, Bob O'Malley
"Bob O'Malley"
Break 95 [96]>
 
cs

처음의 버전과 상당부분 유사하지만, 이름을 물어봤을 때 따옴표 없이 입력이 가능하다는 차이점이 있다. read-line 명령이 엔터가 입력될 때까지 들어간 모든 텍스트를 반환해주기 때문에 이름 뿐만 아니라 공백, 따옴표 등도 입력이 가능하다는 차이점도 있다.