Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

`items.map(&:name)`을 작동하게 만드는 영리한 해킹

배열을 순회하다 보면 반복해서 사용하게 되는 축약 문법이 하나 있습니다. 바로 &: 트릭, 일명 "앰퍼샌드 콜론(ampersand colon)" 또는 "프레첼 콜론(pretzel colon)"입니다. 익숙하지 않은 분들을 위해 작동 방식부터 살펴보겠습니다.

words = ["would", "you", "like", "to", "play", "a", "game?"]

# 이렇게 쓰면...
words.map &:length

# ...사실상 아래와 동일합니다.
words.map { |w| w.length }

저는 한동안 이 &: 문법이 연산자라고 생각했습니다. 하지만 그렇지 않습니다. ActiveSupport에서 시작되어 Ruby 1.8.7에서 공식 기능으로 편입된 영리한 해킹(hack)입니다.

& 연산자

Ruby에서 "&" 문자는 AND 논리 연산 외에도 다른 용도로 사용됩니다. 메서드 인자 앞에 붙이면 피연산자의 to_proc 메서드를 호출한 결과를 블록으로 전달하는 역할을 합니다. 말로 설명하면 다소 복잡하게 들리지만, 예제를 보면 훨씬 이해하기 쉽습니다.

def my_method(&block)
  block.call
end

class Greeter
  def self.to_proc
    Proc.new { "hello" }
  end
end

my_method(&Greeter) # "hello"를 반환합니다

Symbol#to_proc

Symbol을 포함한 어떤 객체에든 to_proc 메서드를 정의할 수 있습니다. 바로 이 점을 활용해 Ruby는 &: 축약 문법을 구현합니다. 개념적으로는 다음과 같은 형태입니다.

class Symbol
  def to_proc
    Proc.new do |item|
      item.send self
    end
  end
end

조금 헷갈리시나요? 핵심은 item.send(self) 부분입니다. 여기서 self는 해당 심볼 자기 자신을 가리킵니다. 즉, 심볼이 자신과 같은 이름의 메서드를 각 항목에 호출하도록 만드는 것입니다.

모두 조합하기

each, map 같은 Enumerable 메서드들은 블록을 받아 각 항목마다 블록을 호출하며, 그때 항목에 대한 참조를 인자로 넘겨줍니다. 앞선 예제에서는 이 블록이 심볼에 to_proc을 호출해 생성된 것입니다.

# &:name은 Proc 객체로 평가되며, 내부적으로 item.send(:name)을 수행합니다
items.map(&:name)

여기서 흥미로운 점은 map 입장에서는 이런 일이 벌어지고 있는지 전혀 알지 못한다는 사실입니다! 실질적인 작업 대부분은 :name 심볼이 수행합니다. 확실히 영리하긴 한데... 제 취향에는 거의 지나치게 영리한 감이 없잖아 있습니다. 하지만 오랫동안 Ruby 표준 라이브러리의 일부였고, 워낙 편리해서 당분간 계속 사용하게 될 것 같습니다. :)