Symbol#to_proc
이전글: 루비를 배우는 좋은 방법, 액티브 서포트 전체보기
객체를 담은 배열이 있습니다. 이 객체들의 id만을 뽑아서 배열을 만들고 싶은 경우 어떻게 할까요? Enumerable#map 메서드와 블럭만 있으면, 너무 쉽게 할 수 있습니다.
- people.map { |person| person.id }
루비 프로그래밍을 하다 보면, 블럭을 자주 사용하게 됩니다. 그런데 그 때마다 쓰게 되는 수직바(|) 사이의 블럭 형식인수가 번거롭게 느껴지는 경우도 많습니다. 위 코드도 다시 보니 |person| person. 이런 식으로 person이 2번 반복되는군요. 루비 커뮤니티에서는 코드 반복이 생기면 지구가 멸망한다고 믿습니다(물론, 농담;).
액티브 서포트에서는 위 코드를 아래처럼 줄여서 쓸 수 있는 문법 사탕을 제공합니다. 한번 보세요
- people.map(&:id)
어떤 사람은 알 수 없는 기호의 나열에 당황할 수도 있겠지만, 그리고 코드가 간결해지고 반복이 사라졌습니다. people 배열의 구성원들의 나이를 모두 더하고 싶다면?
- people.sum { |person| person.age }
위 처럼 쓰는 대신,
- people.sum(&:age)
라고 쓰면 됩니다. 어떻게 이런 축약이 가능해졌을까요? 답은 Hash#to_proc에 있습니다.
루비에서 어떤 메서드를 호출할 때 invoke_method(&…)처럼 &를 사용하면 이는 Proc 객체를 매개변수로 제공하고, 이 객체를 블럭으로 사용합니다.
- >> [1,-1,2].reject {|x| x>0}
=> [-1]
>> positive = Proc.new {|x| x > 0}
=> #
>> [1,-1,2].reject positive
ArgumentError: wrong number of arguments (1 for 0)
from (irb):5:in `reject’
from (irb):5
>> [1,-1,2].reject &positive
=> [-1]
reject 메서드는 주어진 블럭을 이용해 Enumerable에서 특정 조건에 해당하는 원소를 제거합니다. reject 메서드에 Proc 객체를 그냥 넘겼더니, 보통 매개변수로 여겨서 ArgumentError가 발생합니다. reject 메서드는 매개변수를 받지 않는데, 왜 넘겼냐는 것이죠. Proc 객체를 일반 매개변수가 아닌 코드 블럭으로 넘기기 위해서는 앞에 &를 붙여줘야 합니다.
다시, people.map(&:id)로 돌아가봅시다. 루비에서 :id는 심벌을 표현하는 리터럴임을 알고 있습니다. 그리고 &는 Proc 객체를 코드 블럭으로 결합하는 문법입니다. 그런데 :id는 Proc 객체가 아닙니다. 인터프리터는 어떤 선택을 할까요? 루비의 철학인 ‘오리 타입’에 따르면 매개변수가 반드시 Proc 클래스일 필요는 없습니다. Proc처럼 행동하면 됩니다. 그리고 Proc처럼 행동하려면 to_proc 메시지를 처리할 수 있으면 됩니다. 여기서는 :id에 to_proc 메시지를 보냅니다. 그렇다면, Symbol#to_proc을 구현해서 위 문장을 잘 돌아가게 만들 수 있겠군요!
&:id 가 {|x| x.id}와 같은 의미의 코드 블럭으로 변환되기를 원합니다. 이 코드블럭은 {|x| x.__send__(:id)}와 같습니다. 그런데 이 블럭이 매개변수를 받는다면, {|x, args| x._send__(:id, args)}라고 표현할 수 있습니다. 여러 개의 매개변수를 받는다면 {|x, *args| x.__send__(:id, *args)}가 됩니다. x, *args를 합치면 {|*args| args.shift.__send__(:id, *args)}가 됩니다.이제 액티브 서포트에서 구현한 Symbol#to_proc 코드가 이해가 될 것입니다.
- class Symbol
def to_proc
Proc.new { |*args| args.shift.__send__(self, *args) }
end
end
언뜻 보기에, 난해해서 이해하기 힘든 코드로 보이지만, 곱씹어보니 그리 어렵지는 않네요. 짧은 한 줄의 코드에 참 많은 내용을 담고 있습니다. 저는 더 친절한 코드를 선호하지만, 가끔 루비 문법이 주는 매력에 빠져서 재미있는 코드를 만들어내기도 합니다. ![]()
마지막으로 이렇게 만들어진 새로운 문법이 빛을 바라는 예제를 한가지 소개합니다. people 배열에서 관리자 월급의 합을 알고 싶다고 합니다. 보통 루비 코드라면 이렇게 됩니다.
- people.select {|person| person.manager? }.sum {|person| person.salary }
하지만 Symbol#to_proc이 있다면?
- people.select(&:manager?).sum(&:salary)
마음만 먹으면 얼마든지 간결하면서도 아름다운 문법을 만들 수 있는 것이 루비의 매력입니다.
- 강문식
History
Last edited on 06/20/2007 20:54 by deepblue
Comments (0)