다른 현대적인 함수형 언어에 비해 파이썬은 문법이 아름답지 못하다. 가령 다음과 같은 경우를 생각해보자.
xs = 1...1000
y = xs.filter { |x| x % 3 == 0 }.first || -1
이 문법을 파이썬으로 표현하면 어떻게 될까?
xs = range(1, 1000)
y = -1
for x in xs:
if x % 3 == 0:
y = x
위와 같이 좀 더 길고, 값보다는 루프에 초점이 맞추어진 코드가 탄생하고 만다. 그러면, 이걸 어떻게 개선할 수 있을까?
next(x for x in xs if x % 3 == 0)
next((x for x in xs if x % 3 == 0), -1)
builtins
의 next
를 사용하면 위와 같이 쓸 수 있다. 하지만 default 값이 있는 경우 괄호를 한번 더 써줘야하는게 마음에 들지 않는다. 이것을 다음과 같이 클래스로 한 번 감싸주면 어떨까?
class First:
def __init__(self, iterator):
self._iter = iterator
self._default = None
def run(self):
return next(self._iter, self._default)
def or_else(self, default):
self._default = default
return self
위와 같이 first
라는 래퍼 클래스를 만들면 다음과 같이 쓸 수 있다.
First(x for x in xs if x % 3 == 0).run()
First(x for x in xs if x % 3 == 0).or_else(-1).run()
이제 우리가 원하는 만큼의 게으름과 예쁜 문법을 얻게 되었다. 하지만 약간의 불만이 남는다. 파이썬의 컴프리헨션은 아무리 봐도 눈에 잘 들어오지 않는다. 순회의 대상과 술어를 분리하는게 읽는데 도움이 된다. 바꾸어보자.
class FirstV2:
def __init__(self, iterable):
self._iterable = iterable
self._default = None
def filter(self, predicate):
self._iterable = filter(predicate, self._iterable)
return self
def exclude(self, predicate):
self._iterable = filterfalse(predicate, self._iterable)
return self
def run(self):
return next(self._iterable, self._default)
def or_else(self, default):
self._default = default
return self
이제 아래와 같이 사용하면 된다.
FirstV2(range(100, 10000))
.filter(lambda x: x % 3 == 0)
.filter(lambda x: x % 2 == 0)
.exclude(lambda x: x % 12 == 0)
.run()