(본 게시물은 저작권의 문제 발생시 출판사의 요청에 의해 삭제될 수 있습니다.)
메서드 정의
메서드는 def 키워드로 정의한다. 그리고 메서드 이름은 반드시 소문자나 밑줄로 시작해야 하며, 문자, 숫자, 밑줄(_)로 구성된다. 메서드 이름 마지막에는 ?, !, =이 올 수 있다. True나 False를 반환하는 메서드(predicate method, 술어 메서드)에는 이름 끝에 ?를 붙이곤 한다.
1.even? # => false
2.even? # => true
1.instance_of?(Fixnum) # => true
'위험' 하거나 수신자의 값을 바궈버리는 메서드는 이름이 느낌표(!)로 끝나기도 한다. 이는 뱅(bang) 메서드라고 불린다. 예를들어 String 클래스에는 chop과 chop! 메서드가 있다. 첫번째 메서드는 변환된 문자열을 반환한다. 두 번재 메서드는 수신자를 바로 변환한다.
대입문의 좌변에 올 수 있는 메서드 이름에는 이름 마지막에 등호(=)를 붙인다. 오직 ?, !, =만이 메서드 이름 마지막에 허용되는 특수 문자다.
메서드의 매개 변수를 선언할 때는 괄호 안에 지역 변수를 차례대로 적어준다. ( 매개 변수를 둘러싸는 괄호는 필수 사항은 아니다. 이 책에서는 매개 변수가 있을 때는 괄호를 사용하고 그렇지 않으면 생략한다. )
def my_new_method(arg1, arg2, arg3) # 세 개의 인자
# 메서드 본문
end
def my_other_new_method # 인자 없음
# 메서드 본문
end
루비는 인자에 기본값을 정해줄 수 있다. 기본값은 메서드 호출시 인자값이 명시적으로 지정되지 않았을때 기본으로 사용할 값을 의미한다. 기본값은 대입 연산자(=)를 이용해서 정의한다. 기본값에 사용되는 표현식에서는 앞서 나온 인자의 값을 참조할 수 있다.
def cool_dude(arg1="Miles", arg2="Coltrane", arg3="Roach")
"#{arg1}, #{arg2}, #{arg3}."
end
cool_dude # => "Miles, Coltrane, Roach."
cool_dude("Bart") # => "Bart, Coltrane, Roach."
cool_dude("Bart", "Elwood", "Linus") # => "Bart, "Elwood, Linus."
가변 인자 리스트
개수가 정해지지 않은 가변 매개 변수를 전달하거나 하나의 매개 변수로 여러 개의 매개 변수를 모두 처리하고 싶다면 어떻게 해야 할까? '일반적인' 매개 변수를 모두 적어주고 맨 마지막에 오는 가변 매개 변수 이름 앞에 별표(*, asterisk)를 붙여 주면 된다.
def varargs(arg1, *rest)
"arg1=#{arg1}. rest=#{rest.inspect}"
end
varargs("one") # => arg1=one. rest=[]
varargs("one", "two") # => arg1=one. rest["two"]
varargs "one", "two", "three" # => arg1=one. rest=["two", "three"]
가변 길이 매개 변수는 메서드에서는 직접 사용하지는 않지만 상위 클래스에서 같은 이름을 가지는 메서드를 호출하고자 하는 경우에 사용되기도 한다. ( 아래 예제코드에서 super를 매개 변수 없이 호출하고 있다는 점을 주목해야 한다. 이는 상위 클래스에 있는 같은 이름의 메서드를 호출하라는 의미를 가진다. 또한 이때 현재 메서드에 넘겨진 모든 매개 변수를 호출하는 메서드에 넘겨준다)
class Child < Parent
def do_something (*not_used)
# 메서드 본문
super
end
end
이때 매개 변수 이름을 생략하고 *만 사용해도 같은 의미다
class Child < Parent
def do_something(*)
# 메서드 본문
super
end
end
가변 길이 매개 변수는 아래와 같이 매개 변수 리스트의 어디에나 올 수 있다.
def split_aprt(first, *splat, last)
puts "First: #{first.inspect}, splat: #{splat.inspect}, " +
"last: #{last.inspect}"
end
split_apart(1,2)
split_apart(1,2,3)
split_apart(1,2,3,4)
실행 결과
First: 1, splat: [], last: 2
First: 1, splat: [2], last: 3
First: 1, splat: [2, 3], last: 4
맨 처음 매개 변수와 마지막 매개 변수에만 관심이 있다면 앞의 메서드를 다음과 같이 정의하는 것도 가능하다.
def split_aprt(first, *, last)
둘 이상의 가변 길이 매개 변수는 모호하므로 이러한 매개 변수는 반드시 메서드 하나만 정의되어야 한다. 또한 가변 길이 매개 변수에서 기본값을 지정할 수 없다. 가변 길이 변수에는 항상 나머지 변수들에 대한 대입이 끝나고 남은 변수들이 대입된다.
메서드와 블록
메서드를 호출할 때 블록을 결합시킬 수 있다. 일반적으로 결합된 블록은 메서드 안에서 yield를 사용해 호출할 수 있다.
def double(p1)
yield(p1*2)
end
double(3) { | val | "I god #{val}" } # => "I got 6"
double("tom") { | val | "Then I got #{val}" } # => "Then I got tomtom"
마지막 매개 변수 앞에 앰퍼샌드(&)를 붙여준다면 주어진 블록이 Proc 객체로 변환되어 이 객체를 마지막 배개 변수(블록 매개 변수)에 대입한다. 이를 통해 이 블록을 나중에 사용할 수도 있다.
class TaxCalculator
def initialize(name, &block)
@name, @block = name, block
end
def get_tax(amount)
"#@name on #{amoung} = #{ @block.call(amonunt) }"
end
end
tc = TaxCalculator.new("Sales tax") { | amt | amt * 0.0.75 }
tc.get_tax(100) # => "Sales tax on 100 = 7.5"
tc.get_tax(250) # => "Sales tax on 250 = 18.75"
메서드 호출
메서드를 호출할 때는 수신자의 이름을 써 주고 그 뒤에 메서드 이름을 쓰고, 필요한 경우 몇 개의 변수를 써 준다. 마지막으로 맨 뒤에 블록이 올 수 있다. 다음의 예제는 메서드의 수신자와 매개 변수, 블록이 모두 있는 경우다.
connection.download_mp3("jitterbug") { | p | show_progress(p) }
connection 객체는 수신자가 되고 download_mp3는 메서드의 이름이 된다. jitterbug는 매개 변수이며 중괄호로 감싸져 있는 코드가 메서드 호출에 결합되는 블록이 된다. 이 메서드가 호출되는 동안에 루비는 self를 수신자로 설정하며 이 객체에 대해 메서드를 호출한다. 클래스나 모듈의 메서드를 호출할 때, 수신자 위치에 클래스 이름이나 모듈 이름을 적어주면 된다.
File.size("testfile") # => 66
Math.sin(Math::PI/4) # => 0.7071067811865475
수신자를 생략하면, 수신자는 기본값인 현재 객체를 나타내는 self 가 된다.
class InvoiceWriter
def initialize(order)
@order = order
end
def write_on(output)
write_header_on(output) # 현재 객체를 수신자로 호출
write_body_on(output) # 리시버가 지정되지 않았으므로
write_totals_on(output) # self는 변하지 않는다.
end
def write_header_on(output)
# ...
end
def write_body_on(output)
# ...
end
def write_totals_on(output)
# ...
end
end
수신자를 생략했을 때의 처리 방식은 루비가 private 메서드를 다루는 것과 같다. private 메서드는 호출할 때는 수신자를 지정할 수 없으므로 그 메서드는 현재 객체에서 호출할 수 있는 것이어야 한다. 앞선 예제에서 헬퍼 메서드를 private으로 설정하는 것이 좋다. 이 메서드는 InvoiceWirter 클래스 외부에서 호출되어서는 안 되기 때문이다.
class InvoiceWriter
def initialize(order)
@order = order
end
def write_on(output)
write_header_on(output) # 현재 객체를 수신자로 호출
write_body_on(output) # 리시버가 지정되지 않았으므로
write_totals_on(output) # self는 변하지 않는다.
end
private
def write_header_on(output)
# ...
end
def write_body_on(output)
# ...
end
def write_totals_on(output)
# ...
end
end
메서드에 매개 변수 넘겨주기
메서드 이름 다음에는 매개 변수가 따라온다. 메서드 호출 시에 매개 변수 리스트를 둘러싼 괄호를 생략해도 되지만 간단한 케이스를 제외하고는 이 방식을 추천하지 않는다. 사소한 문제로 말썽이 생길 수도 있기 때문이다. 뭔가 망설임이 생긴다면 반드시 괄호를 사용하라
# 객체 obj가 있다고 할 때 아래의 두 표현은 같다.
a = obj.hash
a = obj.hash()
obj.some_method "Arg1", arg2, arg3
obj.some_method ("Arg1", arg2, arg3 )
루비 이전 버전에서는 메서드 이름과 '(' 사이에 공백을 허용하고 있어서 문제가 발생하곤 했다.
메서드는 값을 반환한다
호출된 모든 메서드는 값을 반환한다. 메서드의 반환값은 메서드 실행 중 마지막으로 실행된 표현식의 결괏값이다 .
def meth_one
"one"
end
meth_one # => "one"
def meth_two(arg)
case
when arg > 0 then "positive"
when arg < 0 then "negative"
else "zero"
end
end
meth_two(23) # => "positive"
meth_two(0) # = > "zero"
루비도 물론 수행중인 메서드를 빠져나가는 return 문이 있다. 이 경우에는 return 문의 매개 변수나 매개 변수들이 반환값이 된다. return 문을 쓸 필요가 없는 경우라면 생략하는 것이 루비식 표현이다.
아래의 예제는 return 문을 명시적으로 사용해 반복문을 빠져나가는 예제이다.
def meth_three
100.tims do | num |
squre = num*num
return num, square if squre > 1000
end
end
meth_three # => [32, 1024]
return 의 매개변수로 여러 개의 값이 동시에 넘기면 메서드는 배열의 형태로 값을 반환한다. 배열로 반환값을 모으기 위해서는 병렬 대입문(parallel assignment)를 이용할 수 있다.
num, squre = meth_three
num # => 32
squre # => 1024
메서드 호출 시에 컬렉션 객체 확장하기
메서드 선언부에서 매개변수 이름 앞에 별표(*)를 붙이면 메서드 호출에 사용된 여러 개의 인자가 하나의 배열로 된 매개 변수로 합쳐지게 된다. 역으로 메서드 호출할 때, 컬렉션 객체나 열거자를 확장해서 구성 원소가 각각의 매개 변수에 대응되도록 할 수 있다. 이렇게 하려면 별표(*)를 배열 매개 변수 앞쪽에 붙여주면 된다.
def five(a, b, c, d, e)
"I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5) # => "I was passed 1 2 3 4 5"
five(1, 2, 3, *['a', 'b']) # => "I was passed 1 2 3 a b"
five( *[ 'a', 'b'] , 1, 2, 3 ) # => "I was passed a b 1 2 3"
five (*[1,2], 3, *(4..5)) # => "I was passed 1 2 3 4 5"
루비 1.9부터 가변 길이 매개 변수는 매개 변수 목록의 어디에나 위치할 수 있게 되었기 때문에, 일반적인 매개 변수와 섞어서 사용할 수 있다.
블록을 더 동적으로 사용하기
메서드 호출에 블록을 결합하는 방법은 아래와 같다
collection.each do | member |
# ...
end
일반적인 경우에는 이 정도의 기능만으로 충분하지만 유연함을 필요로 하는 경우를 살펴보자. 계산법을 가르친다고 할때 학생들은 n-plus 테이블과 n-times 테이블이 필요할 것이다. 2-times 테이블을 원함녀 2, 4, 6, 8 ... 을 만들어 반환해 주어야 한다.
print "(t)imes or (p)lus: "
operator = gets
print "number: "
number = Integer(gets)
if operator =~ /^t/
puts( (1..10).collect { | n | n*number }.join(", ") )
else
puts( (1..10).collect { | n | n + number }.join(", ") )
end
실행결과
(t)imes or (p)lus : t
numeber :2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20
if 문에서 계산하는 부분만 빼내면 더 깔끔해질수 있다.
print "(t)imes or (p)lus: "
operator = gets
print "number: "
number = Integer(gets)
if operator =~ /^t/
calc = lambda { | n | n * number }
else
calc = lambda { | n | n + number }
puts( (1..10)collect(&calc).join(", "))
실행결과
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20
메서드의 마지막 매개변수 앞에 앰퍼샌드(&)가 붙어 있다면, 루비는 이 매개 변수를 Proc 객체로 간주한다. 일단 Proc 객체를 매개 변수 리스트에서 빼내서 이를 블록으로 변환한 다음 메서드에 결합하는 식으로 처리한다.
해시와 키워드 인자
해시를 통해 선택적 이름이 있는 인자를 메서드에 넘기는 방식을 많이 사용한다. 예를 들어 MP3 플레이리스트에 검색 기능을 추가한다고 해 보자.
class SongList
def search(field, params)
# ...
end
end
list = SongList.new
list.search(:titles, {genre : "jazz", duration_less_then: 270} )
첫번째 인자는 검색을 통해 반환받고 싶은 값이다. 두 번째 이름은 검색 맥 변수를 모든 해시 리터럴이다 (심벌을 키로 사용하고 있다) 해시를 사용해서 마치 각각의 키워드인 것처럼 사용할 수 있다.
키워드 인자 목록
search 메서드의 내부를 들여다 보자. 이는 필드 이름과 옵션들을 담은 해시를 받는다. 기본설정을 길이 120초로 하고 잘못된 옵션이 없는지 검증하고자 한다. 루비 2.0 이전 코드는 아래와 같다.
def search(field, options)
options = { duration: 120 }.merge(options)
if options.has_key?(:duration)
duration = options:[:duration]
options.delete(duration)
end
if options.has_key?(:genre)
genre = options[:genre]
options.delete(:genre)
end
fail "Invalid options: #{options.keys.join(', ')}" unless options.empty?
# 나머지 구현
end
루비2에서는 메서드에서 직접 키워드 매개 변수를 정의할 수 있다.
def search(field, genre: nil, duration: 120)
p [field, genre, duration ]
end
search(:title)
search(:title, duration: 432)
search(:title, duration: 432, genre: "jazz")
실행결과
[:title, nil, 120]
[:title, nil, 432]
[:title, "jazz", 432]
올바르지 않은 옵션을 넘기면 루비는 에러를 발생시킨다.
def search(field, genre: nil, duration: 120)
p [field, genre, duration ]
end
search(:title, duraton: 432)
실행결과
prog.rb:5:in '<main>': unknown keyword: duraton (ArgumentError)
또한 매개 변수 목록에 없는 해시로 넘어온 매개 변수들을 두 개의 별표(**)를 이름에 붙여 매개 변수로 받을 수도 있다.
def search(field, genre: nil, duration 120, **rest)
p [field, genre, duration, rest ]
end
search(:title, duration: 432, starts: 3, genre: "jazz", tempo: "slow")
실행결과:
[:title, "jazz", 432, {:star=>3, :tempo=>"slow"}]
이를 증명하기 위해 ㅁ매개 변수를 담은 해시를 넘겨서 메서드를 호출해보면 된다.
def search(field, genre: nil, duration: 120, **rest)
p [field, genre, duration, rest ]
end
option = {duration: 432, starts: 3, genre: "jazz", tempo: "slow" }
search(:title, options)
실행결과
[ :title, "jazz", 432, {:stars=>3, :tempo=>"slow"} ]
잘 짠 루비 프로그램은 일반적으로 작은 크기의 많은 메서드를 포함한다. 따라서 메서드를 정의하고 호출할 때 사용할 수 있는 여러 방법에 익숙해지는 것은 많은 도움을 준다.