Elixir 문서의 개선에 기여하다

Posted on Sunday, 15 Jan 2017diary elixir 

최근에 Elixir의 마이너 업데이트 1.4가 릴리즈 되었습니다. 새로운 라이브러리 요소와 여러 편의 기능들이 추가되어 가려웠던 곳을 시원하게 긁어 주었는데요, 특히 IEx에서 반환값을 출력할 때 색이 입혀져 나오는 것은 정말 제 마음에 들었습니다.

하지만 제가 가장 기대했던 것은 Task.async_stream 함수입니다. 제가 진행하고 있는 Serum에서는 여러 페이지를 동시에 빌드하도록 하기 위해 다음과 같은 코드를 썼는데요,

files
|> Enum.map(&Task.async(__MODULE__, :some_task, [&1, src, dest]))
|> Enum.map(&Task.await/1)

이번에 새로 추가된 Task.async_stream을 사용하면 이런 식으로 고쳐 쓸 수 있습니다.

files
|> Task.async_stream(__MODULE__, :some_task, [src, dest])
|> Enum.map(&(elem &1, 1))

이 함수의 특성 상 리턴 값을 처리하는 또 다른 단계가 추가되긴 했지만, 어쨌든 여러 개의 Task를 만들고 대기하는 작업을 한 번의 함수 호출로 할 수 있게 되었습니다. 추가적으로, 시스템의 작업 스케줄러에 의해 동시에 실행되는 Task의 개수도 제어된다는 장점도 있구요.

그런데 이 함수를 Serum에 적용하기 위해 공식 문서를 읽고 IEx에서 시험삼아 몇 번 써 봤는데, 문서에 명시된 동작과 실제 동작에 차이가 있어서 혼란스러웠습니다. 다음은 그 당시의 문서의 일부분입니다.

async_stream(enumerable, module, function, args, options \\ [])

Returns a stream that runs the given module, function and args concurrently on each item in enumerable.

Each item will be appended to the given args and processed by its own task. The tasks will be linked to an intermediate process that is then linked to the current process. This means a failure in a task terminates the current process and a failure in the current process terminates all tasks.

만약 문서에 명시된 대로라면 enumerable의 각각의 원소는 args 리스트의 맨 끝에 추가되어 function에 적용되어야 할 것입니다. 이렇게요.

Task.async_stream([1, 2, 3], __MODULE__, :fun, [:foo, :bar])
==> Task 1: fun(:foo, :bar, 1)
    Task 2: fun(:foo, :bar, 2)
    Task 3: fun(:foo, :bar, 3)

하지만 실제로 이 함수를 써 봤을때 다른 동작을 보였습니다.

iex> defmodule Test do
...    def run_task do
...      [1, 2, 3]
...      |> Task.async_stream(__MODULE__, :foo, [10, 100])
...      |> Enum.to_list
...    end
...
...    def foo(x, y, z) do
...      IO.inspect {x, y, z}
...      x + y + z
...    end
...  end

iex> Test.run_task
{1, 10, 100}
{2, 10, 100}
{3, 10, 100}
[ok: 111, ok: 112, ok: 113]

리스트의 각각의 요소가 함수의 맨 첫번째 인자로 넘겨졌네요! 이는 이 요소들이 args의 맨 처음 위치에 추가되어 함수에 적용되었기 때문에, 즉 append된 것이 아니라 prepend되었기 때문이지요. 이러한 문제를 확인한 사람들이 있나 보기 위해 Google과 GitHub을 검색해 보았지만, 아직 아무도 보지 못한 것 같기에, 잽싸게 이에 대한 이슈를 제출했습니다.

사실 이슈를 제출하고 많이 긴장됐어요. 난생 처음으로 외국의 큰 프로젝트에 이슈를 넣어보았기 때문이기도 하고, 아직도 저는 함수형 프로그래밍 언어나 관련 분야에 익숙하지 않다고 생각하기 때문에 어쩌면 이 분야에서는 “Append”라는 단어가 다른 의미를 갖고 있는 것은 아닌가 하는 별난 생각도 들었구요. 하지만 이슈를 넣은 지 얼마 지나지 않아 Elixir 언어의 원작자인 José Valim으로부터 답변이 왔습니다. 이는 자신의 실수에 의해 잘못 기재된 내용이 맞고, “append”를 “prepend”로 수정한 문서가 master와 v1.4 브랜치에 반영되었다는 말이었습니다.

사실 결과만 따지고 보자면 문서에 있는 단어 하나가 고쳐진 것 뿐이지만, 꽤 가슴 벅찬 경험이 아닐 수 없었습니다. 내가 드디어 대형 오픈소스 프로젝트에 기여를 했구나, 내가 이걸 지금 발견하지 않았다면 나중에 더 많은 사람들이 혼란스러워했을 수도 있었겠구나, 아무튼 여러가지 생각이 머릿속을 오갔습니다. 역시 이런 게 오픈 소스 프로젝트의 묘미가 아닐지요.