[루비 사용자 가이드] 예제 다시 살펴보기

루비/레일스 프로그래밍/루비 사용자 가이드 2007.04.29 15:47

이제 이전에 나왔던 예제를 자세히 살펴봅시다.

다음은 간단한 예제부분에 있던 내용입니다.

def fact(n)
  if n == 0
    1
  else
    n * fact(n-1)
  end
end
puts fact(ARGV[0].to_i)

이게 최초로 코드를 설명하는 부분이니까 각각의 행을 따로따로 자세히 들여다 봅시다.

팩토리얼(Factorials)

def fact(n)

첫 줄에서 def은 함수(정확히는 메소드(method))를 정의하는 명령입니다. 메소드가 무엇인지에 대해서는 나중에 이야기할겁니다. 여기서는 함수 factn으로 참조되는 유일한 인자(argument)만을 받는다는 것을 지정하고 있습니다.

if n == 0

if는 조건을 검사하기 위한 것입니다. 조건이 참이면 다음에 오는 코드가 계산되고, 조건이 거짓이면 else 다음에 오는 코드가 계산됩니다.

1

if 문의 결과 값은 조건이 참인 경우 1입니다.

else

조건이 거짓이면 여기부터 end 사이에 있는 코드들이 계산됩니다.

n * fact(n-1)

조건이 만족되지 않을 때 if식의 값은 nfact(n-1)를 곱한 값입니다.

end

첫 번째 endif 문을 닫는데 사용되었습니다.

end

두 번째 enddef 문을 닫기 위해 사용되었습니다.

puts fact(ARGV[0].to_i)

이 식은 우리의 fact()함수를 커맨드라인에서 제공받은 값을 가지고 호출해서, 그 결과를 출력합니다.

ARGV는 커맨드라인 인자를 저장하는 배열입니다. ARGV의 원소는 모두 스트링이기 때문에 fact()를 호출할 때 인자로 쓰기 위해서는 to_i를 사용해 정수로 변환해 주어야만 합니다. 루비는 펄(perl)과 달리 스트링을 자동으로 정수로 변환하지 않습니다.

만약 이 프로그램에 음수를 입력으로 넣으면 어떤 일이 벌어질까요? 한번 그 버그를 고쳐보시렵니까?

스트링

다음으로 우리는 스트링에 대한 글에서 다룬 퍼즐 프로그램을 살펴볼것입니다. 이 프로그램은 조금 더 길기 때문에 줄에 번호를 붙여서 설명시 참조하겠습니다.

01 words = ['foobar', 'baz', 'quux']
02 secret = words[rand(3)]
03
04 print "guess? "
05 while guess = STDIN.gets
06   guess.chop!
07   if guess == secret
08     puts "You win!"
09     break
10   else
11     puts "Sorry, you lose."
12   end
13   print "guess? "
14 end
15 puts "the word is ", secret, "."

이 프로그램에서는 새로운 컨트롤 구조인 while이 사용되었습니다. while과 그에 대응하는 end 사이에 있는 코드가 지정한 조건이 만족되는 한 반복해서 수행됩니다. 이 프로그램에서는 guess=STDIN.gets이 실제 수행되는 명령(사용자 입력을 받아서 guess라는 이름으로 저장)인 동시에, 조건(만약 입력이 없는 경우 전체 guess=STDIN.gets 식의 값을 담아두는 guess는 nil값을 가지고, while 루프는 중단됩니다)이기도 합니다.

STDIN은 표준 입력 객체입니다. 보통 guess=getsguess=STDIN.gets와 같은 일을 합니다.

2번 줄의 rand(3)은 0부터 2 사이의 수를 무작위로(random) 반환합니다. 이 무작위 수가 words 배열의 원소를 하나 꺼내는 데 사용됩니다.

5번 줄에서 우리는 STDIN.gets를 사용해 표준 입력에서 한 줄을 읽습니다. 만약 EOF (파일의 끝,end of file) 이라면 getsnil을 반환합니다. 따라서 이 while 명령 안의 코드는 사용자가 입력의 끝을 의미하는 ^D(도스(DOS)나 윈도(Windows)에서는 ^ZF6지요)를 입력할 때 까지 반복 수행됩니다.

6번 줄의 guess.chop!guess의 마지막 글자를 지웁니다. 여기서는 항상 guess의 끝 문자가 개행문자(newline)입니다. 왜냐하면 gets가 사용자가 키보드로 입력한 Return 키의 값을 저장하기 때문입니다. 하지만 그 문자는 우리에게는 불필요한 것이므로 여기서 제거합니다.

15번 줄에서는 미리 정한 비밀의 단어를 출력합니다. 그 작업은 puts로 이루어집니다(putsputstring의 약자입니다). puts에 새 개의 인자가 주어졌는데, 각각은 차례대로 출력됩니다. 동일한 작업을 한 개의 스트링만 puts로  전달하면서 할 수 있습니다. 이때 secret#{secret}라고 써서 그것이 그대로 출력되야 하는 리터럴이 아니고 계산되어야 하는 변수라는 점을 분명히 할 수 있습니다:

puts "the word is #{secret}."

많은 프로그래머들이 이 방법이 더 깔끔하다고 생각합니다. 이 방법은 한 개의 스트링을 표시하고, puts에 유일한 인자로 제공되기 때문입니다.

이제 스크립트에서 puts를 출력을 위해 사용하는 것에 익숙해졌습니다. 하지만, 이 스크립트의 4번 줄과 13번 줄에서는 print도 사용합니다. printputs는 완전히 같은 것은 아닙니다. print는 정확하게 주어진 인자를 출력합니다. puts는 인자의 출력과 함께 출력의 줄을 바꾸는 것을 보장해줍니다. print를 4번 줄과 13번 줄에서 사용할 때, 커서는 다음 줄로 이동하지 않고 막 출력된 내용 다음에 머무르게 됩니다. 그로 인해 사용자의 입력을 기다리는 프럼프트를 알아볼 수 있게 해줍니다. 일반적으로 다음의 네가지 출력 함수 호출은 동일합니다:

# puts는 출력될 대상에 개행문자가 있지 않은 경우 개행문자를 추가해줍니다:
puts  "Darwin's wife, Esmerelda, died in a fit of penguins."

# print를 사용시에는 개행문자를 명시적으로 넣어주어야 합니다:
print "Darwin's wife, Esmerelda, died in a fit of penguins.\n"

# 여러 스트링을 +로 붙여서 출력할 수 있습니다:
print 'Darwin's wife, Esmerelda, died in a fit of penguins.'+"\n"

# 혹은 여러개의 스트링을 출력 함수에 전달할 수도 있습니다:
print 'Darwin's wife, Esmerelda, died in a fit of penguins.', "\n"

알아둘 것 한가지: 때때로 텍스트 윈도우는 속도 향상을 위해 버퍼를 사용하여 구현될 수 있습니다. 즉, 각각의 출력 문자를 메모리에 모아서 개행문자를 출력하게 되거나 모인 문자가 일정 갯수 이상이 될 때만 출력해주는 경우가 있습니다. 따라서, 위의 단어 추축 게임 스크립트가 프럼프트를 표시하지 않는 경우, 버퍼링이 주범일 수 있습니다. 이런 일이 벌어지지 않게 확실히 하려면 프럼프트를 출력한 다음에 flush를 호출하면 됩니다. 그렇게 하면 표준 출력 디바이스(STDOUT라는 이름의 객체)에게, "기다리지 말고 버퍼에 있는 내용을 즉시 출력해."하고 명령하게 됩니다.

04 print "guess? "; STDOUT.flush
  ...
13 print "guess? "; STDOUT.flush

사실 우리는 다음 스크립트에서 이미 버퍼를 고려해 작성된 예를 보았습니다.

정규식

마지막으로 정규식에 대해 설명한 부분에서 보였던 예제를 살펴보겠습니다.

01 st = "\033[7m"
02 en = "\033[m"
03
04 puts "Enter an empty string at any time to exit."
05
06 while true
07   print "str> "; STDOUT.flush; str=gets.chop
08   break if str.empty?
09   print "pat> "; STDOUT.flush; pat=gets.chop
10   break if pat.empty?
11   re = Regexp.new(pat)
12   puts str.gsub(re, "#{st}\\&#{en}")
13 end

6번 줄에서 while 문의 조건이 true으로 하드코딩 되어있습니다. 따라서 무한 루프를 구성합니다. 하지만 8번과 10번 줄에서 루프를 탈출하기 위해 break 명령을 사용합니다. 이 두 break는 또한 "if 수식어(if modifier)."의 좋은 예이기도 합니다. if 수식어는 주어진 조건을 만족할 경우에만 if의 왼쪽에 있는 식을 수행합니다. 이것은 논리적으로 오른쪽에서 왼쪽으로 동작한다는 점에서 별난 구조입니다. 하지만 루비에서 이런 기능을 제공하는 것은 많은 사람들이 말을 할 때 사용하는 비슷한 패턴을 본따기 위한 것입니다. 또한 이 문장은 인터프리터에게 조건문에 얼마나 많은 코드가 포함되어야 하는지를 알려주기 위해 end를 사용할 필요가 없다는 점 때문에 간결하다는 장점도 지니고 있습니다. if 수식어는 관습적으로 조건과 명령이 스크립트 한 줄에 들어갈 수 있을 정도로 짧은 경우에만 사용됩니다.

단어 추축 게임과의 사용자 인터페이스 차이를 살펴보세요. 이 스크립트는 사용자가 Return 키만 입력할 경우 종료하게 되어 있습니다. 우리는 존재하지 않는지 검사하지 않고, 입력 스트링이 빈 스트링인지를 검사합니다.

7번과 9번 줄에서 우리는 "파괴적이지 않은(non-destructive)" chop을 사용합니다. 여기서 우리는 gets를 사용해 입력 받으면 항상 들어오게 되는 원치 않는 개행문자를 제거합니다. 이 설명에 덧붙여서, "파괴적인(destructive)" chop도 있습니다. 둘의 차이는 무엇일까요? 루비에서는 보통 '!'나 '?'를 특정 메소드 이름의 뒤에 붙입니다. 느낌표(!, 때때로 "뱅(bang!)"이라고 크게 발음합니다)는 무언가 잠재적으로 파괴적인 것을 표시합니다. 즉, 대상이 되는 것의 값 자체를 변경하는 경우를 말합니다. chop!은 스트링에 직접적으로 작용하지만, chop은 원본 스트링을 손상시키지 않고, 원본에서 마지막 문자만 없앤 복사본을 돌려줍니다. 다음에 둘 사이의 차이를 보여주는 예가 있습니다.

ruby> s1 = "forth"
  "forth"
ruby> s1.chop!       # s1을 바꿉니다.
  "fort"
ruby> s2 = s1.chop   # 변경된 복사본이 s2에 저장됩니다,
  "for"
ruby> s1             # ... 이때 s1에는 변화가 없습니다.
  "fort"

때때로 chompchomp!가 사용되는 걸 볼 수 있습니다. 이것들은 (chop보다) 더 선택적으로 작동합니다. 즉, 스트링의 맨 긑이 개행문자인 경우에만 끝의 문자가 제거됩니다. 따라서 예를 들어 "XYZ".chomp!이라고 해도 아무 변화가 없습니다. 만약 여러분이 둘 사이의 차이를 기억하기 위한 요령이 필요하다면, 무언가를 한 입 베어물기로 결정하기 전에 그게 먹을만한 것인지 결정하는 동물이나 사람과 그에 반해 무조건 잘라내는 도끼를 생각하면 됩니다. (역주-영어로 chop은 "잘라내다"라는 뜻이고, chomp는 "한 입 베어물다"라는 뜻입니다. 이 차이를 chop과 chomp 메소드의 차이를 설명하는 데 사용한 것입니다.)

8번과 10번줄에 다른 명명 규약이 나타납니다. 그것은 물음표(?, 때때로 "허?(huh?)"라고 크게 발음합니다)입니다. 물음표는 true이나 false 중 하나를 반환하는 "서술어(predicate)" 메소드입니다.

11번 줄은 사용자가 입력한 스트링에서부터 정규식 객체를 만듭니다. 실제 작업은 12번 줄에서 이루어집니다. 그 줄에서 gsub가 사용자가 입력한 정규식에 매치되는 부분 스트링을 안시(ANSI) 마크업으로 둘러싼 것으로 전역적으로(globally) 치환(substitute)합니다. 그리고 동일한 줄에서 치환의 결과가 출력됩니다.

우리는 12번째 줄을 다음과 같이 별개의 줄로 분리할 수 있습니다:

highlighted = str.gsub(re,"#{st}\\&#{en}")
puts highlighted

또는 "파괴적인" 방법으로 다음과 같이 할 수도 있습니다:

str.gsub!(re,"#{st}\\&#{en}")
puts str

12번째 줄의 나머지 부분을 다시 살펴봅시다. sten는 1번 줄과 2번 줄에서 텍스트를 역상과 보통으로 표시하도록 만드는 ANSI시퀀스로 정의되어 있습니다. 12번 줄에서 그 둘은 #{}안에 들어가서 그것 자체로 해석됩니다(따라서 우리는 변수 이름들이 출력되는것을 볼 수 없습니다). 그 두 변수 사이에 \\&가 있습니다. 이것은 약간 교묘합니다. 치환할 스트링이 큰 따옴표로 둘러싸여 있기 때문에 두개의 백슬래시는 하나의 백슬래시로 해석됩니다. 따라서 gsub가 실제로 보게 되는 것은 \&입니다. 이것은 패턴과 매치된 내용을 참조할 때 사용하는 특별한 코드입니다. 따라서 새로운 스트링이 출력되면 사용자가 두번째로 입력한 스트링에서 처음 입력한 패턴과 일치하는 부분들이 역상으로 표시됩니다.


신고
Trackback 0 : Comment 1

티스토리 툴바