루비 언어의 기본 디자인은 최초의 객체지향 언어인 스몰토크에서 많은 영향을 받았습니다. 루비는 순수 객체지향 언어이며, 가비지 콜렉션을 지원합니다.

좀더 쉽게 풀어 써 보죠. 순수 객체지향 언어라는 말은, 루비의 모든 것은 오브젝트라는 뜻입니다. C++나 Java와 달리 정수, 실수, 문자열 등의 primitive type 또한 오브젝트이며, 모든 클래스는 Object라는 최상위 클래스를 상속합니다. 가비지 콜렉션을 지원한다는 말은, Java와 같이 새로운 클래스를 만들 때 메모리를 할당하고 해제하는 작업(new/delete)을 직접 해줄 필요가 없다는 뜻입니다.

그럼 간단한 클래스를 하나 만들어볼까요.

# 클래스 이름은 항상 대문자로 시작해야 합니다.
# CamelCase 스타일(각 단어의 첫글자를 대문자로 하여 모두 붙여쓰는 스타일)로
# 명명하는 것이 보통입니다.
class Cat
  # 클래스 생성자 메소드의 이름은 항상 initialize입니다.
  # 메모리 해제는 가비지 컬렉터가 알아서 해주기 때문에 소멸자는 만들지 않아도 됩니다.
  def initialize(name)
    # 인자를 하나 받아 그 인자를 @name 이라는 인스턴스 변수에 저장합니다.
    @name = name
  end

  # 클래스 내부에서 메소드를 정의하는 방법은 일반 함수를 정의하는 것과 동일합니다.
  def say
    puts @name + " says meow!"
  end
end

# Cat 클래스의 인스턴스를 만들기 위해서는 Cat.new 메소드를 호출해야 합니다.
# new 메소드는 내부적으로 initialize를 호출합니다.
a_cat = Cat.new("Nyang")

# 인스턴스의 메소드를 호출해봅시다.
a_cat.say
#=> Nyang says meow!

생성자 메소드 initialize와 멤버 메소드 say를 가진 클래스 Cat이 만들어졌습니다.
여기서 주목할 부분은 인스턴스 변수 @name입니다. 루비의 인스턴스 변수(instance variable)는 C++/Java의 멤버 변수(member variable), 혹은 필드(field)에 해당하는 개념이며, 하나의 인스턴스 내부에서만 공유되는 변수입니다. 루비의 인스턴스 변수는 항상 변수 이름 앞에 '@'를 붙여 줘야 합니다.

이번에는 만들어진 클래스를 상속해봅시다

# 클래스를 상속하기 위해서는 < 연산자를 사용합니다.
class Lion < Cat
  def say
    # 키워드 super의 사용법은 Java(혹은 MS VC++)의 super와는 다르므로 혼동하지 마세요.
    # 루비의 super는 자신의 상위 클래스에 존재하는 같은 메소드를 찾아 호출합니다.
    # 이 경우 Tiger#say 내에서 호출되었으므로 상위클래스의 say인 Cat#say를 호출할 것입니다.
    super
    puts "...oops, roars!"
  end
end

a_lion = Lion.new("Simba")
a_lion.say
#=> Simba says meow!
#=> ...oops, roars!

이제 위에서 만든 Cat 클래스에서, 고양이의 이름을 바꾸고 싶습니다. 어떻게 하면 좋을까요?

결론부터 말하자면, 루비의 모든 인스턴스 변수는 private, 즉 클래스 내부에서만 접근 가능합니다. (접근 제어 키워드인 private, protected, public에 대해서는 다음 챕터에서 좀더 자세히 다루겠습니다.) 인스턴스 변수의 접근 권한을 public으로 바꿀 방법도 없습니다. 그래서 항상 getter와 setter 메소드(private 변수에 접근하기 위해 사용하는 public 메소드)를 사용해야 합니다.

그런데 루비의 getter와 setter 메소드는 Java의 getName과 setName과 같은 지루한 이름 대신, 좀더 특수한 문법을 허용합니다.

# 기존의 Cat 클래스에서는 @name에 직접 접근하는 것이 불가능합니다.
a_cat.name = "kitty" #=> NoMethodError

# Cat 클래스를 재정의합시다.
class Cat
  def initialize(name)
    @name = name
  end

  def say
    puts @name + " says meow!"
  end

  # 지난 챕터에서 언급했듯이, 메소드에 return 문이 존재하지 않으면
  # 자동으로 마지막 줄의 리턴값을 돌려줍니다.
  # 따라서 이 메소드는 @name의 값을 리턴하는 함수입니다.
  def name
    @name
  end

  # 루비 메소드는 마지막 글자에 한해 '=', '?', '!'와 같은 특수문자를 허용합니다.
  def name=(new_name)
    @name = new_name
  end
end

a_cat = Cat.new("nyang")
a_cat.name = "kitty"
puts a_cat.name
#=> "kitty"

위에서 눈치챌 수 있는 것처럼, 인터프리터는 a_cat.name문을 a_cat의 메소드 name()을 호출하는 것으로 해석하고, a_cat.name = "kitty" 문은 a_cat의 메소드 name=()을 인자 "kitty"로 주어 호출하는 것으로 해석합니다. (루비 메소드를 호출할 때는 괄호를 붙이지 않아도 된다는 것, 기억하시죠?) 결과적으로, 인스턴스 변수를 외부에서 투명하게 볼 수 있는 public 변수처럼 다루는 getter와 setter 메소드가 만들어졌습니다.

그러나 여기서 끝이라면 재미가 없죠. 루비는 인스턴스 변수인 @name을 외부에서 접근하기 위해 6줄이나 사용하는 불필요한 낭비를 허용하지 않습니다. 다음과 같은 일종의 매크로를 사용하면 같은 일을 더 짧게 할 수 있습니다.

class Cat
  # 이 매크로는 인스턴스 변수 @name에 getter인 name 메소드와
  # setter인 name= 메소드를 자동으로 만들어 줍니다.
  # getter만 만들고 싶다면 attr_accessor 대신 attr_reader를,
  # setter만 만들고 싶다면 attr_writer 매크로를 사용할 수 있습니다.
  # 여러 개의 인스턴스 변수에 getter와 setter를 만들고 싶다면,
  # attr_accessor :name1, :name2 와 같이 쉼표로 연결해주면 됩니다.
  attr_accessor :name

  def initialize(name)
    @name = name
  end

  def say
    puts @name + " says meow!"
  end
end

이와 같이 쓰면 위에서 사용한 2개의 메소드와 의미상 동일한 메소드가 자동으로 만들어집니다.

지난 챕터에서는 1부터 10까지 더하는 코드를 지겹게 만들었었죠. 코드의 반복을 피하기 위해 함수로 만들어봅시다.

# array를 인자로 받아 배열의 모든 원소를 더해서 리턴하는 함수 sum_all 을 만들어 봅시다.
def sumall(array)
  total = 0
  array.each {|num| total += num}
  return total
end

puts sumall(1..10)
# => 55

def 키워드로 시작해서 end 로 끝내면 됩니다. C/Java와 다른 점은 인자와 리턴값의 타입을 명시하지 않는다는 거겠죠? 타입을 명시하지는 않지만 타입 체크를 하지 않는 것은 아닙니다. 엉뚱한 타입을 넘겼을 경우 타입 에러가 돌아옵니다.

# 위 코드에서 계속됩니다.
puts sumall(35)

이 코드를 실행시키면 다음과 같은 에러를 뱉습니다.

method.rb:3:in `sum_all': undefined method `each' for 35:Fixnum (NoMethodError) from method.rb:8

인자로 넘겨준 35는 FixNum, 즉 정수 타입이기 때문에 35.each 함수를 부를 수 없다는 뜻의 에러입니다.

루비에서는 인자를 받지 않는 함수도 만들 수 있고, 인자에 기본값을 줄 수도 있으며, 인자의 개수를 바꿔가며 넣어줄 수도 있습니다.

# 1에서 45 사이의 랜덤한 정수 6개를 돌려주는 함수를 만들어봅시다.
# 인자를 받지 않는 함수의 경우 괄호는 생략해도 됩니다.
def lotto
  # rand(n)은 0..n-1 사이의 랜덤한 정수를 반환합니다.
  [rand(45)+1, rand(45)+1, rand(45)+1, rand(45)+1, rand(45)+1, rand(45)+1]
end

# p는 puts와 비슷하지만 결과값의 내용을 보여주는, 디버깅에 주로 사용되는 함수입니다.
p lotto
# => [19, 37, 13, 42, 22, 37]

위에서 선언한 함수 lotto에는 return 키워드가 없는 걸 볼 수 있습니다. 루비의 함수는 return 이 없을 경우 마지막 줄의 리턴값(지난 챕터에서 말씀드렸듯, 루비의 모든 실행문에는 리턴값이 존재합니다.)을 리턴합니다. 따라서 위와 같은 한줄짜리 함수는 return 없이도 간단히 쓸 수 있습니다.

이번에는 인자를 받지 않을때는 위 함수와 똑같이 동작하지만, 정수의 개수와 최대값을 인자로 넣어줄 수도 있는 함수를 만들어 봅시다.

def lotto(num = 6, max = 45)
  # Array.new는 새 배열을 만들어주는 함수입니다.
  # 인자 하나(num)와 블록 하나( {rand(max)+1} )를 받아서,
  # num 크기의 배열을 {rand(max)+1} 로 채워줍니다.
  Array.new(num) {rand(max)+1}
end

p lotto
# => [30, 20, 23, 32, 5, 29]
p lotto(3, 25)
# => [25, 3, 10]

임의 개수의 인자를 받으려면 인자 이름 앞에 *을 붙여줍니다. 함수 내에서는 해당하는 이름의 배열로 자동 변환됩니다.

def hello(*list)
  list.each {|name| puts "Hello, " + name + "!"}
end

hello("C", "C++", "Java")
# => Hello, C!
# => Hello, C++!
# => Hello, Java!

2. There is more than one way to do it

저는 국민학교 때 본 정체불명의 컴퓨터 학습만화를 통해 처음 GW-BASIC이라는 프로그래밍 언어가 있다는 것을 알게 되었습니다. (2진수에 대해서도 덤으로 알게 되었지만 GW-BASIC과 2진수 사이에 도무지 무슨 관련이 있는지는 한참동안 알지 못했습니다.) GW-BASIC은 함수도 없고, 함수가 없으니 당연히 지역변수도 없고, (모든 변수는 전역변수였습니다.) 배열 외에는 쓸만한 자료구조도 없고, 끔찍한 줄번호와, GOTO, IF, FOR 만으로 모든 것을 헤쳐나가야 하는 언어였습니다. 그때 제가 책을 참고해가며 사흘을 낑낑거려 처음 짠 코드는 바로 이거였습니다.

10 REM "용서하세요. 그땐 아무도 코드 들여쓰기를 가르쳐주지 않았답니다!"
20 SUM = 0
30 FOR I = 1 TO 10
40 SUM = SUM + I
50 NEXT I
60 PRINT SUM

문법이 맞는지 지금은 가물가물하군요. BASIC을 배워본 적이 없는 행운아분들을 위해 이 코드를 간단히 설명하자면, for문으로 I를 1부터 10까지 증가시켜 가며 SUM에 그 값을 더해서 출력하는 간단한 코드입니다. 이 코드를 실행시키면 55라는 답이 나왔습니다.

BASIC이라는 언어는 1963년에 개발되었습니다. Matz가 처음 Ruby를 발표한 것은 1995년이지요. 30년이라는 시간이 흘렀지만 for 문의 사용법은 크게 변하지 않은 것처럼 보이는군요.

# 이제는 들여쓰기를 배웠지요!
sum = 0
for i in 1..10
    sum = sum + i
end
puts sum

C 스타일 for 문과는 많이 다릅니다만 BASIC의 for 문과는 거의 똑같아 보이는군요.

그러나 똑같은 코드를 루비에서는 이렇게도 짤 수 있습니다.

# 변수 i 주변을 둘러싼 괄호는 shift + \ 를 누르면 나오는 세로줄 기호입니다.
sum = 0
(1..10).each { |i| sum = sum + i }
puts sum

Smalltalk에 익숙한 분이 아니라면 정말 생소한 코드일 겁니다. 저도 처음 봤을 땐 뭐 이런 코드가 있어? 싶었으니까요. 그러나 루비에 조금만 익숙해지면 for 문 따위 아예 잊어버리게 되실 겁니다. each가 훨씬 재밌거든요!

똑같은 코드를 조금만 풀어 써 봅시다. 위 코드는 사실 다음 코드와 동일한 의미를 갖습니다.

sum = 0
list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
list.each { |i| sum = sum + i }
puts sum

그러면 이제 이 코드가 대체 뭘 하자는 건지 조금 알게 될 것 같습니다. 1..10 은 사실 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]을 나타내는 방법 가운데 하나입니다. (사실 두 가지 표현은 약간 다른 의미를 갖지만 지금은 중요하지 않으니 넘어갑시다.) { |i| sum = sum + i } 이 부분은 변수 i를 받아서, sum = sum + i 를 계산하는 코드 조각이지요. (코드 조각이 대체 뭐하는 거야? 하는 의문은 조금만 미뤄둡시다.) 그러면 list.each 의 역할은?
"list 객체의 모든 원소에 대해 { ... } 안에 들어있는 코드 조각을 각각 실행해라" 라는 의미를 갖는 멤버 함수입니다.

감이 잘 안오시나요? 그럼 이렇게 해보죠.

# ["R", "u", "b", "y"]의 원소들을 각각 str로 받아 puts str로 출력합니다.
["R", "u", "b", "y"].each { |str| puts str }
# 루비의 for 문은 기존의 언어에 익숙한 사람들을 위한 일종의 서비스입니다.
# 사실 루비의 for 문은 each를 이용해 구현되었기 때문에
# 다음 코드의 의미는 완전히 같습니다.
for str in ["R", "u", "b", "y"]
    puts str
end
R
u
b
y

짜잔. 리스트의 내용을 전부 출력하는 아주 간단한 코드가 만들어졌군요.

for 루프를 도는 두 가지 방법을 배웠습니다. (사실 더 있지만 여러분의 머리를 복잡하게 만들고 싶지 않군요. :P ) if문은 어떨까요?

# 제 나이가 이렇다는 건 아니구요 :P
age = 19

if age >= 40
    puts "대선에 출마할 수 있습니다"
elsif age >= 19
    puts "투표할 수 있어요"
else
    puts "투표할 수 없어요"
end
#=> 투표할 수 있어요

#if문과 실행문을 한줄에 붙여 쓰려면 사이에 then을 넣어줘야 합니다.
if age >= 18 then puts "술을 마실 수 있어요" end
#=> 술을 마실 수 있어요

아하. 간단합니다. C의 전통적인 { ... } 괄호 대신 if .. (then) .. elsif .. else .. end로 블록을 구분한다는 점이 다르군요.

그러나 여기부턴 또 새로운 세계입니다.

age = 19
# 조건문과 실행문의 위치를 이렇게 뒤집을 수도 있어요.
# 한줄에 붙여쓰고 싶다면 사실 이게 더 편리합니다.
# end 문을 안 넣어도 된다는 장점이 있거든요.
puts "술을 마실 수 있어요" if age >= 18
#=> 술을 마실 수 있어요

# 루비의 모든 문장은 항상 반환값을 갖습니다. 심지어 if문도 반환값을 갖지요.
# 여기서는 if문이 string을 반환하기 때문에 puts가 값을 받아서 출력할 수 있습니다.
puts (if age >= 19 then "투표할 수 있어요" else "투표할 수 없어요" end)
#=> 투표할 수 있어요

if 문을 사용하는 방법도 한두가지가 아니군요. 뭐가 이렇게 복잡한가요?

그건 루비의 모토가 "There is more than one way to do it" 이기 때문입니다. 어떤 일을 하는데 방법은 한 가지가 아니라는 거죠.

지금까지 제가 접해본 언어들은 대부분 한 가지 일을 하는데 한가지, 많아야 두어가지 정도의 방법밖에 없었습니다. 루비는 좀 다릅니다. 입으로 말을 할 때 단어를 고르는 것처럼, 코딩을 할 때도 더 간단하게, 더 짧게, 더 재미있게, 더 우아하게 등등등 가운데 취향에 맞는 걸로 골라서 코딩할 수 있습니다.
물론 너무 코드를 간단하고 우아하게 만드는데 재미를 붙이다 보면 오히려 알아볼 수 없는 코드를 만들어내는 경우도 생기고, 정작 할일을 하는데 오히려 방해가 되는 경우도 생기죠. 사실 요즘 인기있는 또다른 언어인 파이썬은 그런 이유로 전통적인 "There is only one way to do it" 을 고수하고 있습니다.
마음에 드는 쪽으로 고르시면 됩니다. 그치만 루비 쪽이 좀더 재미있지 않나요?

루비에는 물론 while문도 있습니다.

sum = 0
i = 1
while i <= 10
    sum = sum + i
    i += 1
end
puts sum

# => 55

while을 실행문 뒤에 붙이는 것도 가능합니다. begin ... end 문을 사용하고 뒤에 while을 붙이면 됩니다.

sum = 0
i = 1
# begin ... end 는 C/Java의 { } 블록처럼 블록을 나타내는 루비의 방법입니다.
begin
    sum = sum + i
    i+=1
end while i <= 10
puts sum

# => 55

마지막으로 case .. when .. else .. end 만 살펴보고 지겨운 조건문들은 건너뛰도록 하죠.

i = 3

case i
when 1
    # i가 1이라면 1을 출력합니다.
    puts "1"
when 2, 3, 4
    # 여러 조건을 지정해 줄 수 도 있습니다.
    puts "2..4"
when 5..7
    # 범위도 지정해 줄 수 있구요
    puts "5..7"
when 8, 9..10
    # 여러 개의 범위나 조건을 같이 쓸 수도 있지요
    puts "8..10"
else
    # case문에서 else는 C/Java의 default와 같은 역할을 합니다.
    puts "1..10 사이의 수가 아니네요"
end

#=> 2..4

C/Java에서처럼 break 문을 쓰지 않아도 된다는 점이 편리한데, 이걸 혹시 단점이라고 생각하실 분이 있을지도 모르겠습니다. C/Java의 switch 문은 break 문을 쓰지 않고 여러 조건을 이어서 써 주면 여러 개의 조건에서 동일하게 동작하도록 코드를 짤 수 있지요. 그러나 루비는 여러 조건을 콤마로 구분해서 같이 지정해 주면 의도는 같지만 더 알기 쉬운 코드를 짤 수 있습니다. 루비의 승리네요 +ㅅ+

프로그래머의 미덕은 부지런함이 아니라 게으름이라고 합니다.
게으른 프로그래머일수록 더 효율적인 코드를 만들고, 다른 게으른 이들의 인생을 더 편하게 만들어줄 줄 알기 때문입니다.

게으른 프로그래머는 두자리 수 이상의 곱셈을 절대 손으로 하지 않습니다.
게으른 프로그래머는 반복되는 귀찮은 작업을 사람이 직접 손으로 하는 것을 죄악이라 믿습니다.
게으른 프로그래머는 또한 코드 반복이 일어나면 하늘이 무너진다고 믿습니다.

여기 게으른 우리들을 위한 프로그래밍 언어 루비가 있습니다.

루비는 마츠모토 유키히로(서양 쪽에서의 애칭은 Matz입니다.)라는 일본인이 만든 인터프리터 언어입니다. 펄, 파이썬, 스몰토크 등 기존의 스크립트 언어에서 이것저것 장점을 따다가 합쳐 만들어냈습니다. 루비는 코드의 반복을 죄악이라 믿고, 읽기에 귀찮지 않아야 하며, 무엇보다 코딩을 할 때 스트레스를 받지 않아야 한다고 믿는 사람들이 만든 언어입니다.

튜토리얼을 보고 대충 따라오시다 보면 다른 "귀찮은" 언어로는 돌아갈 수 없는 자신을 발견할지도 모릅니다.
다만, 가끔 설명이 좀 과하게 짧아도 이해해 주시기 바랍니다. 저도 게으른 프로그래머랍니다 :)



-1. if (programmer) { goto 0; } else { return; }

The three chief virtues of a programmer are: Laziness, Impatience and Hubris.
  - Larry Wall

시작하기 전에

당신은 프로그래밍 언어에 익숙하신가요? 이 튜토리얼은 당신이 C/C++/Java 가운데 한가지 언어에 익숙하다는 가정 하에 진행됩니다. 당신이 이 언어들에 별로 익숙하지 않다는 얘기는 아예 프로그래밍 언어를 접해본 적이 없다는 얘기겠지요. 그런 분들에게는 초보자를 위한 다른 튜토리얼을 추천합니다. 루비는 프로그래밍을 처음 해보는 사람에게도 훌륭한 안내자가 됩니다만, 이 튜토리얼은 프로그래밍을 해본 사람들에게 루비를 좀더 빠르게 이해시키기 위해 썼습니다.

C/C++/Java 가운데 한가지 언어에 익숙하시다면 아래 튜토리얼에 나온 예제들을 따라해 보세요. 모든 프로그래밍 언어 안내서에서 하는 말이지만, 백문이 불여일견(百問 不如一見)이고 백견이 불여일타(百見 不如一打)입니다. 프로그래밍 언어를 배우는데 직접 쳐보는 것보다 더 빠른 방법은 없습니다.

당신이 혹시 python이나 perl에 익숙하다면 한 2챕터 정도까지는 그냥 훑어만 보셔도 무방합니다. 인터프리터 언어에 이미 익숙한 분들이라면 루비가 정말 재밌어지는 건 한 2~3챕터 지난 다음부터일 겁니다.

그러면 정말로 시작해볼까요.



0. puts "Hello, ruby!"

Zero is the most natural number.
  - Dijkstra

루비를 시작하려면 먼저 인터프리터가 있어야겠죠. 윈도우 사용자라면 여기서 인스톨러를 다운받아서 그냥 설치하시는 게 제일 속편합니다. 안 게으르시다면 인터프리터랑 인터랙티브 쉘을 따로 받으셔도 되고, 아예 소스 코드를 다운받아서 컴파일하셔도 됩니다만, 우리는 게으르니까요. 다른 운영체제의 경우엔 죄송하지만 그냥 넘어가겠습니다. 맥 OS에는 그냥 구입할때부터 설치되어 있고, 리눅스도 데비안이나 레드햇 계열은 아주 쉽게 설치할 수 있다는군요.

설치하셨다면 실행 -> cmd 해서 커맨드 프롬프트를 열어 주세요. 그 다음 irb라고 치면 다음과 같은 프롬프트가 나옵니다.

irb(main):001:0>


irb는 인터랙티브 루비(interactive ruby)의 약자라고 합니다. 루비 커맨드를 실행하면 결과가 바로 돌아오는 편리한 쉘 환경입니다. 단도직입적으로, 여기에 우리의 첫번째 프로그램을 써볼까요.

irb(main):001:0> puts "Hello, ruby!"
Hello, ruby!
=> nil

아하, 쉽습니다. irb에서 나가시려면 exit라고 치면 됩니다.

다른 방법으로는 루비 인터프리터인 ruby를 사용할 수 있습니다. irb는 코드를 저장하고 고치고 할 수 없으니 대개의 루비 프로그램이 이 방법으로 실행되겠죠? 선호하시는 텍스트 에디터를 열어 아까 쓴 코드를 hello.rb라는 이름의 파일로 저장합니다. 그리고 커맨드라인에서 다음과 같이 인터프리터를 부릅니다.

ruby hello.rb

결과는 마찬가지로 Hello, ruby! 를 출력합니다.

Hello, ruby!

통합 개발환경에 더 익숙하신 분은 같이 인스톨러에 딸려오는 통합 개발환경인 SciTE나 FreeRIDE를 함께 사용해 보세요. 코딩하고 F5를 누르면 결과창에 결과가 출력될 겁니다.
eclipse에 익숙하신 분들은 eclipse에서 RDT를 설치하시면 루비를 지원합니다.
추가: 요즘 루비 커뮤니티에서 떠오르고 있는 IDE로 netbeans라는 IDE와 Aptana Studio라는 IDE가 경쟁중입니다. Aptana Studio는 예쁘고 기능이 다양하지만 아직 버그가 많다는 것이 많은 사람들의 의견이고, netbeans가 보다 안정적이고 사용하기도 편하다는군요. 저는 요즘들어 netbeans를 사용하고 있습니다.



1. print "ruby is easy!\n"

Example isn't another way to teach, it is the only way to teach.
  - Albert Einstein

(이 장에서 진행되는 코드는 irb에서 실행하시는 것이 편합니다.)


실행하는 법을 알았으니 방금 실행한 코드가 무엇을 뜻하는지 알아볼까요.

puts는 문자열을 출력하는 루비의 내장 함수입니다. 정확히 말하면, 문자열을 출력하고 뒤에 줄바꿈(\n)을 덧붙입니다. "Hello, ruby!"는 말할 것도 없이 문자열 타입이지요. 그런데 Java나 C/C++에 익숙하신 분이라면 puts의 문법이 좀 이상하게 느껴질 겁니다. 함수를 호출하는데 괄호가 없군요?

루비에서 함수를 호출할 때 괄호는 그냥 옵션입니다. 아래 코드 두 줄이 의미하는 바는 정확히 같습니다.

puts "Hello, ruby!"
puts("Hello, ruby!")


저는 괄호를 붙이지 않는 쪽을 선호합니다. 기존 언어에 익숙하신 분들은 좀 읽기 어려우실지도 모르지만 조금만 익숙해지면 훨씬 읽기 편하다는 것을 알게 될 겁니다.

문자열을 출력하되 뒤에 줄바꿈을 덧붙이지 않으려면 puts 대신 print를 쓰면 됩니다. 줄바꿈을 덧붙이느냐 아니냐의 차이 뿐 두 함수의 동작은 동일합니다.

irb(main):001:0> puts "ruby is easy!"
ruby is easy!
=> nil
irb(main):002:0> print "ruby is easy!"
ruby is easy!=> nil
irb(main):003:0>

문자열만 자꾸 출력하고 있으려니 프로그래밍 언어라는 생각이 안 드는군요. 이제는 진짜 계산을 해봅시다.

irb(main):001:0> 4+3
=> 7
irb(main):002:0> 3-8
=> -5
irb(main):003:0> 3*2
=> 6
irb(main):004:0> 4/3
=> 1
irb(main):005:0> 3**3
=> 27
irb(main):006:0> 2**100
=> 1267650600228229401496703205376


쉽군요. 벌써 짐작하셨겠지만 3**3은 33을 의미합니다. 그런데 잠깐, 마지막 숫자의 크기가 너무 터무니없지 않나요? 2100? 100비트 정수?

루비가 다루는 정수에는 크기 제한이 없습니다. 무제한 크기의 Bignum 타입을 기본으로 지원하기 때문에 "혹시 0x7fffffff보다 커지면 어쩌지..." 이런 고민 없이 그냥 계산하시면 됩니다. "그래도... 혹시라도... 무제한 크기의 정수 타입이 계산 효율이 떨어지면 어쩌죠?" 라고 물으시는 분들은, 그냥 32비트가 넘는 계산을 하지 마세요!

상수 말고 변수도 있겠지요?

irb(main):001:0> a=2
=> 2
irb(main):002:0> b=3.2
=> 3.2
irb(main):003:0> c=a+b
=> 5.2
irb(main):004:0> d="Ruby"
=> "Ruby"
irb(main):005:0> e=" is fun!"
=> " is fun!"
irb(main):006:0> f=d+e
=> "Ruby is fun!"
irb(main):007:0> puts c, f
5.2
"Ruby is fun!"
=> nil

변수를 선언할 때 "int a=2;" 라던가 "float b=3.2;" 같이 타입을 선언해줄 필요는 없습니다. 변수의 타입 정도는 루비가 알아서 처리해 줍니다.

루비의 모든 것은 오브젝트입니다. 숫자도 오브젝트, 문자열도 오브젝트죠. 그래서 숫자나 문자열에서 함수를 호출할 수 있습니다.

irb(main):001:0> -3.abs
=> 3
irb(main):002:0> 5.2.to_i
=> 5
irb(main):003:0> "Hello".length
=> 5
irb(main):004:0> "Hello".reverse
=> "olleH"
irb(main):005:0> "4.3".to_f
=> 4.3
irb(main):006:0> "4".to_i
=> 4
irb(main):007:0> -3.to_s
=> "-3"

슬슬 재밌어지죠?

진짜 프로그래밍 언어라면 배열이 있어야겠죠. 배열은 꺾은 괄호 안에 콤마로 구분해서 표현합니다.

irb(main):001:0> a = [-3, "Array", 2.6]
=> [-3, "Array", 2.6]
irb(main):002:0> a[1]
=> "Array"
irb(main):003:0> a[0] = -4
=> -4
irb(main):004:0> a
=> [-4, "Array", 2.6]
irb(main):005:0> a.reverse
=> [2.6, "Array", -4]
irb(main):006:0> a.length
=> 3
irb(main):007:0> [3, 1, 2].sort
=> [1, 2, 3]

문자열이나 상수와 마찬가지로 타입을 선언해줄 필요가 없습니다.
게다가 보시다시피 배열 안에 들어있는 오브젝트의 타입들이 서로 달라도 개의치 않습니다.
좀더 장난을 쳐볼까요?

irb(main):001:0> a = [-4, "Array", 2.6]
=>
[-4, "Array", 2.6]
irb(main):002:0> a[3] = 2.8
=> [-4, "Array", 2.6, 2.8]

irb(main):003:0> a << 3.0
=> [-4, "Array", 2.6, 2.8, 3.0]
irb(main):004:0> b = a + ["a", "b", "c"]
=> [-4, "Array", 2.6, 2.8, 3.0, "a", "b", "c"]
irb(main):005:0> c = b - ["Array", "b", 2.6, 3.0, -4]
=> [2.8, "a", "c"]
irb(main):006:0> c[2] = [1, 2]
=> [1, 2]
irb(main):007:0> c
=> [2.8, [1, 2], "c"]
irb(main):008:0> c * 3
=> [2.8, [1, 2], "c", 2.8, [1, 2], "c", 2.8, [1, 2], "c"]

보시다시피 크기도 마음대로 변하고, 덧셈, 뺄셈에 곱셈까지 가능합니다! 결과가 직관적이라는 건 말할 것도 없겠지요.

인덱스를 가지고도 장난을 쳐봅시다.

irb(main):001:0> a = ["A", "B", "C", "D", "E"]
=> ["A", "B", "C", "D", "E"]
irb(main):002:0> a[1..3]
=> ["B", "C", "D"]
irb(main):003:0> a[2, 2]
=> ["C", "D"]
irb(main):004:0> a[-1]
=> "E"
irb(main):005:0> a[-2]
=> "D"
irb(main):006:0> a[2..-1]
=> ["C", "D", "E"]
irb(main):007:0> a[1..2] = ["F", "G"]
=> ["F", "G"]

irb(main):008:0> a
=> ["A", "F", "G", "D", "E"]


짐작하셨겠지만 [1..3]은 1에서 3까지의 원소를 가져오고 [2, 2]는 2부터 시작해서 2개의 원소를 가져옵니다.
-1이나 -2 같은 음수를 가지고 인덱싱하는 것은 생소하게 느껴지겠지만 굉장히 편리합니다. 잘 이해가 안되신다면 이렇게 생각하세요

인덱스 크기가 0..4까지 있을 때

실제 인덱스:  0  1  2  3  4  0  1  2  3  4
음수 인덱스: -5 -4 -3 -2 -1  0  1  2  3  4


특히 [2..-1] 같은 인덱싱은 "2에서 맨 끝까지"를 표현할 수 있는 아주 편리한 방법입니다.

문자열도 일종의 배열이므로, 문자열에서 할 수 있는 웬만한 장난은 문자열에서도 다 가능합니다. 다만 주의하실 것이 있습니다.

irb(main):001:0> a = "ruby is fun!"
=> "ruby is fun!"
irb(main):002:0> a[0..3]
=> "ruby"
irb(main):003:0> a[0]
=> 114
irb(main):004:0> a[0, 1]
=> "r"
irb(main):005:0> a[0] == a[0, 1]
=> false
irb(main):006:0> a[0] == ?r
=> true

보시다시피, 문자열의 일부를 잘라내면 여전히 문자열이지만 문자열에서 글자를 하나만 인덱싱하면 하나의 바이트로 취급하기 때문에 정수가 반환됩니다.
그러나 첫번째 글자가 r인지 알기 위해서 꼭 r의 아스키 코드인 114를 알아야 하는 건 아닙니다. 위에서처럼 a[0, 1]로 한 글자 크기의 문자열을 잘라내서 "r"과 비교하는 방법이 있고, 그거 말고도 r이라는 글자 앞에 ? 기호를 붙이면 r에 해당하는 아스키 코드로 자동으로 변환해줍니다.

마지막으로 열어볼 자료형은 해시(Hash)입니다. 해시는 키워드(key)를 넣어주면 값(value)이 돌아오는 자료구조입니다.
C++ STL의 std::map이나, Java의 HashMap, 또는 TreeMap을 생각하시면 됩니다.

irb(main):001:0> a = {"ten" => 10, "hundred" => 100, "thousand" => 1000}
=> {"thousand"=>1000, "hundred"=>100, "ten"=>10}
irb(main):002:0> a["ten"]
=> 10
irb(main):003:0> a["thousand"]
=> 1000
irb(main):004:0> a["sparta"] = 300
=> 300
irb(main):005:0> a
=> {"thousand"=>1000, "hundred"=>100, "ten"=>10, "sparta"=>300}
irb(main):006:0> a[0] = "zero"
=> "zero"
irb(main):007:0> a
=> {0=>"zero", "thousand"=>1000, "hundred"=>100, "ten"=>10, "sparta"=>300}
irb(main):008:0> a["array"] = [1, 2, 3]
=> [1, 2, 3]
irb(main):009:0> a
=> {0=>"zero", "thousand"=>1000, "array"=>[1, 2, 3], "hundred"=>100, "ten"=>10, "sparta"=>300}

간단하지만 편리하죠.

이렇게 해서 루비의 기본 자료형을 전부 다 보셨습니다. 손이 좀 아프지만 보람찬 챕터였군요.