3 minute read

Elixir 표준 라이브러리 중 key-value 자료구조에 규칙을 가진 것처럼 보이는 함수가 있다. get, fetch, fetch! 와 같은 값을 가져오는 함수이다. 자료 구조에서 값을 가져오는 함수로 fetch 를 사용하는 게 낯설었다. 어떤 규칙으로 설계했는지 궁금해졌다.

요약 by human

  • Elixir에서 key-value 자료구조에서 값을 가져오는 함수가 3개
    • get - 없으면 default 값
    • fetch - {:ok, value} 형식으로 패턴매칭하기 좋게 리턴
    • fetch! - 없으면 에러를 내는 터프한 함수
  • Erlang은 get, find 이름을 사용
  • Erlang 네이밍 규칙이 더 마음에 든다
    • get 은 빠르게 값을 가져오고 fetch 는 DB나 웹에서 가져오는 식으로 이름을 붙여왔음

get, fetch, fetch! 구분

When you see the functions get, fetch, and fetch! for key-value data structures, you can expect the following behaviours:

  • get returns a default value (which itself defaults to nil) if the key is not present, or returns the requested value.
  • fetch returns :error if the key is not present, or returns {:ok, value} if it is.
  • fetch! raises if the key is not present, or returns the requested value.

Naming conventions — Elixir v1.18.4 - hexdocs.pm

예전에는 이런 네이밍 규칙(naming convention) 문서가 없었던 것 같다. 표준 라이브러리 문서 보강을 많이 했다더니 정말이다.

’없을 수도 있지’의 get 함수. 없을 때는 디폴트 값을 리턴한다.

iex> Keyword.get([], :a)
nil
iex> Keyword.get([a: 1], :a)
1
iex> Keyword.get([a: 1], :b)
nil
iex> Keyword.get([a: 1], :b, 3)
3

세 번째 인자로 디폴트 값을 넘긴다.

’패턴 매칭 편하게 해줄께’의 fetch 함수.

iex> Keyword.fetch([a: 1], :a)
{:ok, 1}
iex> Keyword.fetch([a: 1], :b)
:error

{:ok, value} 를 리턴해서 패턴 매칭을 편하게 할 수 있다.

case Keyword.fetch([a: 1], :a) do
  {:ok, val} -> IO.puts("찾았다! #{val}")
  :error -> IO.puts("없다!")
end

fetch 함수가 아니라 get 함수를 호출한다면 디폴트 값을 리턴한 것인지 찾은 key의 값인지 구분할 수가 없다. has_key? 함수와 같이 써야 확신할 수 있는데, fetch 함수를 쓰면 한 번에 가능하다.

’없으면 큰일난다’의 fetch! 함수

iex> Keyword.fetch!([a: 1], :a)
1
iex> Keyword.fetch!([a: 1], :b)
** (KeyError) key :b not found in: [a: 1]

getfetch 의 사전적 의미

to obtain (something) (무언가를) 얻다

dict.naver.com

to go after and bring back (someone or something) 누군가나 어떤 것을 찾아가서 다시 데려오거나 가지고 돌아오다

dict.naver.com

get 과 다르게 fetch 는 찾아가서 가지고 돌아온다는 뜻이다. 그래서 더 시간이 걸리는 뉘앙스를 전달할 수 있다. 뭔가 찾아서 가져온다는 뜻이니 반드시 가져온다는 뉘앙스도 전달할 수 있을 것 같다.

Erlang은 어떤 함수 이름을 사용하나? - get, find

fetch 라는 함수 이름을 Erlang에서 가져왔다고 생각했으나 보기 좋게 빗나갔다.

Elixir의 fetch 는 Erlang의 find 와 비슷하다.

1> Map = #{foo => 42}.
2> maps:find(foo, Map).
{ok,42}
3> maps:find(bar, Map).
error

Elixir의 get 은 디폴트 인자를 명시적으로 넘긴 get 과 비슷하다.

1> Map = #{foo => 42}.
2> maps:get(foo, Map, 0).
42
3> maps:get(bar, Map, 0).
0

Elixir의 fetch! 는 디폴트 인자가 없는 get 과 비슷하다.

1> Map = #{foo => 42}.
2> maps:get(foo, Map).
42
3> maps:get(bar, Map).
** exception error: {badkey,bar}

Erlang의 get, find 규칙이 더 마음에 든다

없는 영어 살림에 뭔가 찾아서 가져와서 오래 걸릴 수 있다는 fetch 라는 용어를 허탈하게 써버려서 아깝다는 생각이 들었다. get 은 캐시가 있어서 바로 리턴할 수 있고 fetch 는 웹이나 DB 등에서 가져오지만 느리다는 뜻으로 사용하고 있었기 때문이다.

find 는 찾으니깐 없을 수도 있지. 이런 뉘앙스를 전달해서 Erlang의 네이밍 규칙이 더 마음에 든다.

Erlang에도 fetch 를 안 쓰는데 왜 이런 단어를 사용했을까? Ruby와 관련이 있을 수 있겠다고 생각했다. Elixir는 Ruby의 영향을 많이 받았다. Elixir를 만든 Jose Valim이 Rails 코어 팀 멤버였기 때문이다. Ruby hash에서 사용하는 fetch 함수에서 가져오지 않았을까?

마치며

Elixir에서 사용하는 get, fetch, fetch! 함수가 처음에는 혼란스러웠다. 값을 가져오는 함수가 왜 이렇게 많아? 패턴매칭에 유용해서 fetch 함수의 진가를 알아봤고 그 외에는 get 함수를 편하게 사용하고 있다. fetch! 함수는 쓸 일이 없는데, 언젠가는 쓸 일이 있을 것 같다.

Erlang의 get, find 규칙이 더 마음에 든다. fetch 단어를 빠르게 값을 가져오는 get 과는 달리 시간이 오래 걸리는 작업을 나타내는 데 쓰고 있기 때문이다.

뭐 그래도 규칙을 잘 정하고 지키기만 한다면 잘 따르다 보면 금방 익숙해진다. Jose Valim도 처음엔 Ruby hash의 fetch 함수 이름이 마음에 들지 않았을 것 같다는 재미있는 생각이 들었다.