'2011/09'에 해당되는 글 2건

  1. 짜증... 2011/09/30
  2. 게으른 프로그래머를 위한 루비 튜토리얼 #5 (3) 2011/09/15

짜증...

from 분류없음 2011/09/30 13:51
http://infuture.kr/1033

이 글의 논지는 별 문제가 없는데... 도킨스를 비난하는 논리가 이기적 유전자 읽어보지도 않고 도킨스 비난하는 종교인들 논리랑 너무 비슷하더라.

그래서 소셜 댓글 어플 달려있는 걸로 몇마디 했더니 리플이 다 지워졌다. 블로그에 javascript로 연결시켜 주는 외부 서비스인 듯 하니 뭐 일부러 지운 건 아니고 주인장의 실수로 링크를 깨뜨린 걸 수도 있겠으나... 어느쪽이던 다시 말 꺼내기 싫어서 그만두었다.

내가 단 리플 내용은 대충 이렇다.

이기적 유전자 제목만 대충 읽고 도킨스를 비난하는 사람이 참 많네요. 도킨스가 책 안에서도 그 후의 저서에서도 여러번 밝혔듯 이기적인 것은 유전자일 뿐 유전자의 표현형인 생물이 이기적이라는 얘기가 아닙니다.

뭐 이 부분은 다른 사람들도 리플로 많이 한 얘기고... 블로그 원문에는 이런 문단이 있다.

여기에 리처드 도킨스가 쓴 '이기적 유전자'가 출간된지 40년이 되어가는데도 여전히 과학도서 중 베스트셀러로 건재한 것을 보면 그만큼 왜곡된 다윈주의가 우리 사회에 얼마나 많이 남아있고 조직을 이끄는 경영자들을 포함한 많은 이들의 사고의 틀을 얼마나 강하게 형성하고 있는지 가늠할 수 있습니다. 도킨스가 스스로 '이기적 유전자는 사이언스 픽션으로 읽어야 한다. 상상력을 자극하기 위해 쓰였기 때문이다'라고 고백했지만, 사람들은 그 책의 내용이 사실이고 유일한 관점으로 왜곡하는 듯하여 안타깝습니다.

이 문단만 보면 도킨스의 저서가 사실이 아니고 SF라는 것을 도킨스가 자기 입으로 밝혔는데도, 많은 사람들이 아직도 사실이 아닌 주장을 신봉하는 것처럼 보인다. 그런데 블로그 글쓴이가 인용한 원문은 사실 이기적 유전자 저자 서문의 첫 줄이다. 이 내용은 다음과 같이 이어진다.

이 책은 마치 과학소설인 것처럼 읽어야 한다. 상상력을 자극하기 위해 쓰여졌기 때문이다. 그러나 이 책은 과학소설이 아니고 과학이다. 클리셰이긴 하지만 '픽션보다 기이하다'는 말은 내가 진실에 대해 가진 느낌을 정확히 표현하고 있다.
아래는 영어 원문.
This book should be read almost as though it were science fiction. It is designed to appeal to the imagination. But it is not science fiction: it is science. Cliche or not, 'stranger than fiction' expresses exactly how I feel about the truth.

뭐 오해받기 딱 좋게 글을 쓴 도킨스의 책임도 일부 없지 않다. 대중과학서 첫 줄에 이 책은 과학소설로 읽으라니... 30주년 기념판 서문에서는 <Selfish Gene>이 아니라 <Immortal Gene>이라고 지으라는 출판업계 사람의 충고를 들을걸 그랬다고 후회하는 대목도 나온다. 아무리 그래도 앞의 두 문장만 따서 인용해 놓고 책 내용이 사실이 아닌 양 하는 것은 저술가이기에 앞서 과학자인 도킨스에 대한 모독이라 하겠다.

그래서 악의적 인용이 오해를 불러일으키는 것 같습니다... 라고 했으나 말이 심하다고 하시는데... 음 정말로 말이 심한 것은 <'이기적 유전자'는 허구다> 라는 글 제목이 아닌가 싶은데. 아무튼 그 뒤는 리플이 지워져서 더이상 말을 섞지 못했다.
루비 튜토리얼 4편을 쓰고 나서 3년이 지났습니다. 음음. 그간 많이 게을렀군요. 변명을 해보자면 결혼(!)이라던지 하는 여러가지 개인적 사정이 있었답니다. 기다려주신 분이 있다면 죄송합니다.

그럼 5편을 바로 시작해볼까요.

지금까지는 재미없지만 몰라서는 안될 기본적인 사항들을 다뤘습니다. 기본적인 화면출력, 자료구조, 분기문과 반복문, 클래스 같은 것을 다루었죠. 이제 조금 생소하지만 정말 재밌는 부분으로 넘어가 봅시다. 여기부턴 다른 언어에서는 만나보기 힘든 내용이니 조심조심 따라와 주세요.

오늘은 파일을 열어볼 겁니다. 파일 읽고 쓰기는 참 쉽습니다.

# 파일은 open 함수를 이용해서 열 수 있습니다.
# "test.txt" 파일을 쓰기 모드("w")로 열어봅시다.
# 주의! 윈도우에서는 "wb" 모드로 여는 것을 추천합니다.
f = open("test.txt", "w")

# open 명령으로 파일 오브젝트를 열어 f 라는 변수에 넣었습니다.
# 이제 1편에서 보았던 puts 함수를 쓸 수 있습니다.
f.puts "Matz"

# print 함수는 뒤에 newline이 붙지 않는 것 기억하시죠?
# 윈도우에서는 \n 대신 \r\n 이 newline입니다.
f.print "Steve Jobs\n"

# write 함수도 있습니다. print와 거의 같습니다.
# 마찬가지로 윈도우에서는 \n 대신 \r\n 을 써줍시다.
f.write "Bill Gates\n"

# 아무리 게을러도 다 쓴 파일은 반드시 닫아야 훌륭한 프로그래머가 됩니다.
f.close

자 이제 메모장이나 여러분이 사랑하는 어떤 텍스트 에디터로든지 파일을 열어보면 세 사람의 이름이 한줄씩 써 있을 겁니다.

이게 어디가 다른 언어에서 만나보기 힘든 내용이냐구요? 에이... 조금만 기다려 보세요.

파일을 읽기도 해봐야죠.

# 아까 그 파일을 읽기 모드 "r"로 읽습니다.
# 주의! 쓸 때와 마찬가지로 윈도우에서는 "rb" 모드로 여는 것을 추천합니다.
f = open("test.txt", "r")

# read 함수는 파일 전체를 읽어서 문자열로 저장합니다.
string = f.read

puts string

# 파일을 썼으면 닫아야죠.
f.close

Matz
Steve Jobs
Bill Gates

쉽죠? 그렇지만 파일 전체를 읽어서 메모리에 저장하는 건 큰 파일을 다뤄야 할 때는 현명하지 못한 방법이죠. 파일을 한줄한줄 읽어봅시다.

f = open("test.txt", "r")

# 2편에서 봤던 each 함수 기억하세요?
# 파일 오브젝트에는 each_line 함수가 있습니다!
# 파일을 한줄씩 읽어서 line 변수에 저장하고, line 변수를 print 함수로 출력합니다.
f.each_line { |line| print line }

f.close

Matz
Steve Jobs
Bill Gates

그럴싸하죠?

{ |line| print line } 요 부분이 오늘의 주인공, 블록(block)입니다. 블록은 인자를 받을 수 있는 임시 코드 조각으로 생각하시면 됩니다. 여기서는 |line| 이 인자가 되고 print line 이 코드 조각이 되는 것이죠.

f.each_line { |line| print line }

이 코드에서 f.each_line 함수는 파일을 한줄한줄 읽어들인 후 line 인자에 넣어 블록으로 전달합니다. 우리가 아까 쓴 파일에는 3개의 이름이 있으니, 블록은 3번 실행되겠죠.

루비에는 배열에서 사용한 each 함수나 파일에서 사용한 each_line 함수처럼 코드 블록을 인자처럼 사용할 수 있는 여러가지 함수들이 있습니다. 재미있는 것만 몇가지 살펴볼까요?

# 모든 정수에는 times 함수가 존재합니다.
# 블록을 해당 숫자만큼 반복 실행하는 함수입니다.
3.times { puts "knock" }

knock
knock
knock


# There is more than one way to do it! 기억하시나요?
# { ... } 블록 대신 do ... end 로 감싸서 사용할 수도 있습니다.
# 의미는 완전히 동일합니다.
3.times do
    puts "knock"
end

knock
knock
knock


# 문자열을 한글자씩 잘라주는 each_char 함수도 유용합니다.
"ABC".each_char do |c|
    puts c
end

A
B
C


# 1편에서 봤던 자료구조 Hash 기억하시죠?
h = {"one" => 1, "two" => 2, "three" => 3}

# Hash에도 each 함수가 있습니다.
# Hash의 each 함수는 key와 value를 쌍으로 블록에 넘깁니다.
h.each do |key, value|
    puts key
    puts value
end

one
1
two
2
three
3


# map 함수는 배열의 모든 값에 블록 내용을 실행한 새로운 배열을 리턴합니다.
# 배열의 값에 2를 곱해 볼까요?
[1, 2, 3, 4].map {|x| x * 2}
# => [2, 4, 6, 8]

# 특정 조건을 만족하는 값을 찾는 것도 select 함수로 간단히 해결됩니다.
# 1, 2, 3, 4 가운데 짝수만을 리턴하게 해봅시다.
[1, 2, 3, 4].select { |x| x % 2 == 0 }
# => [2, 4]

# 정수 오브젝트에 포함된 even? 함수를 이용하면 더욱 간단합니다.
# 함수 뒤의 물음표도 함수 이름에 포함되므로 붙여써야 하는 점에 주의하세요.
[1, 2, 3, 4].select { |x| x.even? }
# => [2, 4]

재미있죠? 블록을 받아 동작하는 다양한 함수들이 있기 때문에 루비 코드는 간결하고 아름다워집니다. 서로 비슷한 일을 하지만 아주 조금씩 다른 동작 때문에 비슷비슷한 루프를 만들어야 했던 어떤 언어들과 비교하면 참으로 상쾌하죠.

블록을 받는 함수는 우리도 만들 수 있습니다. 조금 전까지 열었다 닫았다 했던 그 파일 기억하시죠? 파일을 열고 닫는 코드를 반복해서 썼었죠. 솔직히 그런 반복은 지겹습니다. 우리는 게으르잖아요. 파일을 필요할 때 열고 할일이 끝나면 알아서 닫게 하고 싶으면 어떻게 하면 될까요?

def test_open
    # 파일 열고 닫기가 지겨워서 함수에 담아보려고 합니다.
    myfile = open("test.txt", "r")

    # 이 한줄이 마술의 핵심입니다.
    yield myfile

    # 파일을 닫습니다.
    # 이미 닫힌 파일을 두번 닫으면 에러가 일어나므로
    # 닫혀 있는지 먼저 체크해야 합니다.
    if not myfile.closed?
        myfile.close
    end
end

test_open do |f|
    f.each_line do |line|
        print line
    end
end

Matz
Steve Jobs
Bill Gates

대체 무슨 일이 일어난 건가요?

이 코드는 한줄한줄 따라가면서 설명해 보겠습니다.

먼저 test_open 함수를 정의했죠. 함수 안에는 처음보는 키워드 yield가 포함되어 있군요. 안에 yield 문이 들어있는 함수는 "아 이 함수는 블록을 붙여서 사용하는 함수구나" 하고 생각하시면 됩니다.

test_open do |f|
    ...
end

test_open 함수를 블록과 함께 호출하면 일단 블록은 무시하고 먼저 함수가 실행됩니다. 함수의 첫번째 줄로 가봅시다.

    myfile = open("test.txt", "r")

파일을 열어서 myfile 변수에 담았죠

    yield myfile

yield는 함수에 주어진 블록을 실행하는 부분입니다. yield 뒤에 인자를 주면 해당 인자가 블록에 전달되지요. 우리도 블록으로 따라가 볼까요.

test_open do |f|
    ...
end

해당 블록은 do ... end 부분이고 |f| 를 인자로 받는 것을 알 수 있죠. 아까 yield myfile로 이 블록을 호출했으니 인자 f 에는 아까 저장된 myfile의 값이 저장됩니다.

    f.each_line do |line|
        print line
    end

이곳에서 f의 내용이 한줄씩 화면에 출력되고 블록은 종료됩니다.

블록의 내용이 종료되면 context는 다시 원래 함수의 원래 위치로 돌아가서 실행을 계속합니다. yield의 다음줄은 파일 닫는 구문이었죠. 논리 부정 연산자 not 도 봐둡시다.

    if not myfile.closed?
        myfile.close
    end

파일을 안전하게 닫고 함수는 정상적으로 종료됩니다.

어떻습니까? 파일 닫기를 내가 굳이 부르지 않아도 마지막에 자동으로 호출해주는 안전한 함수가 쉽게 완성되었죠. 이 함수는 사실 루비가 기본으로 제공하는 함수를 직접 손으로 구현해본 것입니다. open 함수는 블록과 함께 부르면 정확히 우리의 test_open 함수처럼 동작합니다. 정말 그런지 확인해볼까요?

# nil 은 빈 값을 의미합니다. Java의 null이나 C/C++ 의 NULL 에 해당합니다.
# 여기서는 비어있는 변수 myfile을 만들어 봅시다.
myfile = nil

open("test.txt", "r") do |f|
    # 블록 인자는 블록 바깥에서 접근할 수가 없습니다.
    # 여기선 나중에 열어보기 위해 외부 변수인 myfile에 담아봅시다.
    myfile = f

    # 파일 내용을 출력한 후 닫습니다.
    puts f.read
end

# 블록과 함께 호출했기 때문에, open 함수의 실행이 끝나면 파일은 이미 닫혀 있습니다.
# myfile.read는 파일이 이미 닫혀 있다는 내용의 에러를 일으킵니다.
myfile.read

Matz
Steve Jobs
Bill Gates
tutorial5-3.rb:16:in `read': closed stream (IOError)
    from tutorial5-3.rb:16:in `<main>'


재미있으셨나요? 머리가 아프신가요? 아직 이정도에 좌절하시면 안됩니다. 오늘 내용을 정리할 겸, 제가 다음 튜토리얼을 쓸 때까지 시간벌기를 할 겸 (...), 연습문제를 하나 드리죠. 1편부터 5편까지의 내용을 이용해서 my_times 함수를 구현해 보세요. my_times 함수는 다음과 같이 동작해야 합니다.

my_times(3) do
    puts "knock"
end

knock
knock
knock

어떻게 하면 블록을 원하는 숫자만큼 실행하게 할 수 있을까요?

힌트 : yield 키워드는 다른 루비 함수처럼 인자 없이도 사용할 수 있습니다.

연습문제 답은 밑에 접어두겠습니다.

답 확인해보기