엘릭서의 파이프라인 연산자는 value |> func1() |> func2()와 같이 데이터를 순차적으로 처리하는 방식을 제공합니다. 루비에서는 ("go " * 3).upcase.sub(/ $/, '!')와 같이 메서드 체이닝이나 URI.open(_1)과 같은 블록을 활용한 .then 메서드를 통해 유사한 흐름을 구현할 수 있습니다. 과거 루비 2.7에서 |> 연산자 도입 시도가 있었으나, 기존 기능과 차이가 없어 혼란만 가중되어 철회된 바 있습니다. 또한, 루비에는 메서드 참조(first-class method references)가 없어 URI.method(:open)처럼 객체를 생성해야 하는 점도 파이프라인 연산자 도입의 걸림돌로 작용했습니다.
그럼에도 불구하고 저자는 파이프라인 연산자 도입에 대한 새로운 논의와 파이썬의 pipe_operator 라이브러리, 특히 @elixir_pipe 데코레이터가 AST(추상 구문 트리) 변환을 통해 동작하는 방식에 영감을 받아 루비에서 유사한 실험을 진행했습니다.
구현 방식
구현된 방식은 다음과 같습니다:
-
pipe키워드를 사용하여 메서드를 데코레이터처럼 표시합니다. -
해당 메서드 내부에서는
>>연산자가 파이프 연산자처럼 작동합니다. -
각 단계에서는 이전 단계의 결과를
_변수로 참조할 수 있습니다. -
_참조를 생략하고 메서드만 지정하면, 이전 단계의 결과가 해당 메서드의 첫 번째 인자로 자동 전달됩니다.
예시:
ruby
pipe def repos(username)
username >>
"https://api.github.com/users/#{_}/repos" >>
URI.open >>
_.read >>
JSON.parse(symbolize_names: true) >>
_.map { _1.dig(:full_name) }.first(10) >>
pp
end
동작 원리
이 구현의 핵심은 런타임에 코드를 실행하는 것이 아니라, pipe 데코레이터가 적용될 때 메서드의 소스 코드를 로드하고, parser gem을 사용하여 AST로 파싱한 뒤, 원하는 파이프라인 동작을 수행하도록 AST를 변환합니다. 이후 unparser를 사용하여 새로운 AST를 루비 코드로 변환하고, 이를 대상 클래스에서 eval하여 메서드를 재정의하는 방식입니다. 즉, 로드 타임에 문법 매크로처럼 동작하여 실제 실행되는 코드는 _ = username; _ = "url/#{_}"; _ = URI.open(_); ...와 같은 형태로 변경됩니다. 이 방식은 코어 객체에 대한 몽키 패치 없이 빠르고 효율적으로 작동합니다.