Elixir 의 Enum 모듈을 직접 구현해보자.
구현을 하기 전에 먼저 Enum 모듈에 대해서 알아보자.
링크에서 확인할 수 있듯이 Enum 모듈은 Enumerable 한 대상을 input 으로 받는다.
Enumerable 은 Elixir 에서 미리 정의한 프로토콜이다.
자세한 사항은 위 enumerable 링크를 타고 문서를 확인하면 된다.
Enum 구현 시 대상을 Enumerable 로 하게되면 가독성이 떨어진다.
그러므로 List 타입만을 대상으로 하는 MyEnum 을 구현해보자.
MyEnum 으로 구현할 함수 list 는 아래와 같다.
- all?/2
- each/2
- filter/2
- split/2
- take/2
all?/2
전체 element 가 주어진 함수에 대해 true 를 반환하면 true, 하나라도 false 면 false 이다.
defmodule MyEnum do def all?([], _fun), do: true def all?([head | tail], fun), do: fun.(head) and all?(tail, fun) end
iex 로 확인해보자.
each/2
주어진 함수를 각 element 를 인자로 실행한 뒤 :ok 를 반환한다.
defmodule MyEnum do # ... def each([], _fun), do: :ok def each([head|tail], fun) do fun.(head) each(tail, fun) end end
filter/2
주어진 함수에 인자로 element 를 넣어서 실행했을 때 true 를 반환하는 element 만 list 에 담아서 반환한다.
defmodule MyEnum do # ... def filter(list, fun), do: do_filter(list, fun, []) defp do_filter([], _fun, acc), do: acc defp do_filter([head|tail], fun, acc) do if fun.(head) do do_filter(tail, fun, acc ++ [head]) else do_filter(tail, fun, acc) end end end
split/2
두개의 리스트로 분리해서 튜플에 담아 반환한다.
이 때 인자로 전달받은 갯수만큼만 첫번째 배열에 저장하고 나머지는 두번째 리스트에 저장한다.
음수일 때 동작은 마지막부터 갯수를 세기 때문에 따로 처리가 필요하다.
아래 구현에서는 Enumerable.count 를 사용했는데, count 함수를 별도로 구현해도 괜찮다.
split 구현이 아닌 코드를 최대한 생략하기 위해서 이미 구현된 Enumerable 의 count 를 사용했다.
defmodule MyEnum do # ... def split(list, count) when count >= 0 do do_split(list, count, [], list) end def split(list, count) when count < 0 do {:ok, list_count} = Enumerable.count(list) split_position = max(0, list_count + count) do_split(list, split_position, [], list) end defp do_split([], _count, first, second), do: {first, second} defp do_split(_list, 0, first, second), do: {first, second} defp do_split([head | tail], count, first, _second), do: do_split(tail, count - 1, first ++ [head], tail) end
take/2
인자로 주어진 만큼의 element 만 리스트에 담아서 반환한다.
defmodule MyEnum do # ... def take(list, count) when count >= 0, do: do_take(list, count, 0, []) def take(list, count) when count < 0 do {:ok, list_count} = Enumerable.count(list) from = max(0, list_count + count) do_take(list, abs(count), from, []) end defp do_take([], _count, _from, acc), do: acc defp do_take(_list, 0, _from, acc), do: acc defp do_take([head | tail], count, 0, acc), do: do_take(tail, count - 1, 0, acc ++ [head]) defp do_take([_head | tail], count, from, acc) when from > 0, do: do_take(tail, count, from - 1, acc) end