함수형 원리로 리팩터링하는 예제 2 - 고차함수, 일급객체

Raymond Yoo·2023년 12월 11일
0
post-thumbnail

함수형 프로그래밍의 독특한 특징은
평범한 변수 뿐만 아니라
함수조차도 변수처럼 사용할 수 있다는 점이다.
그래서 함수형 프로그래밍에서는
모든 함수가 언제든 고차함수가 될 수 있다.
고차함수는
파라미터로 어떤 함수를 입력받거나
출력값으로 함수를 반환하는 함수를 의미한다.
둘 중에 하나만 하더라도 고차함수라고 부른다.
그리고 이렇게 파라미터로 전달하거나 반환값으로 받는 함수를
일급객체라고 부른다.

이런 고차함수, 일급객체 라는 특성을 활용하면
함수형 프로그래밍의 원리를 따르는 코드는
프로그래머에게 레고 블럭 놀이를 하는 것 같은 인상을 준다.
전체 프로그램을 완성하기 위해서
작은 단위의 함수를 모아서 사용하고
작은 단위 함수를 모아서 만들어낸 중간 단위 함수를
적절하게 조합해서 큰 단위 함수를 만들고
큰 단위 함수를 또 모아서 재배치한 후에 호출하면
마침내 전체 프로그램을 완성하게 된다.

이게 어떤 느낌인지는 예제 코드를 통해서 확인해보자.

예제코드 링크

user info list 구현 코드

사용자 목록을 가지고
필요한 정보를 추출하는 기능을 개발한다고 가정해보자.

사용자 정보는 아이디(id), 이름(name), 나이(age) 를 담고 있다.

상세한 요구조건을 정리하면 다음과 같이 두 가지가 있다.

  • 나이가 30살 이상인 사용자들의 이름 목록 확인하기
  • 나이가 30살 미만인 사용자들의 나이 목록 확인하기

우선 여기서는 생각나는대로 구현해본다.

_filter, _map 함수 01
_filter, _map 함수 02

앞에서 구현한 내용을
함수형 프로그래밍의 원리 중 하나인
고차함수 원리를 적용해서 수정해보자.

새롭게 두 개의 함수를 만들었다.

_filter 함수는
어떤 항목들의 집합에서 명시한 조건에 맞지 않는 항목을 버리고
조건에 맞는 항목만 추출하는 기능이다.

_map 함수는
어떤 항목들의 집합의 모든 요소를 하나씩
파라미터로 사용해서 명시한 함수를 호출한 뒤
그 결과값을 처음과 똑같은 순서, 똑같은 구조로 반환하는 기능이다.

_each

어떤 항목들의 집합 요소를 하나씩 순회하며
파라미터로 전달해서 명시한 함수를 호출하는 기능이다.

여기서부터는 고차함수로 구현하는게
레고 블럭 놀이같은 프로그래밍이 가능하다는 것을
조금씩 느낄 수 있는데
앞에서 구현한 _filter, _map 함수 내부에서
_each 함수를 사용할 수 있다.

예제코드로 가져온 코드는
실제로 실행해보면 빈 리스트([])를 출력하는데
그 이유는 람다 함수(lambda function)에서
람다 함수 스코프 내에 있지 않은 변수를
캡쳐(capture, capturing)하지 못해서 벌어진 이슈다.
이 이슈를 해결할 수 있다면
반복문을 사용하는 곳마다 _each 로 대체해서
레고 블럭 놀이가 가능해진다.

_curry, _curry_right

_curry 함수는
func(a, b, c, d) 라는 함수가 있을때
func(a)(b)(c)(d) 형태로 호출할 수 있도록 만드는 기능이다.
이 기능이 유용한 경우는 함수 func 를 호출하는 시점에
해당 함수에 전달할 아규먼트로 필요한 a, b, c, d 를
모두 알고 있지 못할때
중간 단계에 해당하는 함수를 미리 만들어두고
나중에 필요한 파라미터를 채워서
최종결과값을 알아낼 때 사용할 수 있다.

_curry_right 함수는
func(a, b, c, d) 라는 함수가 있을때
func(d)(c)(b)(a) 형태로 호출할 수 있도록 만드는 기능이다.
위의 _curry 함수와 차이는 파라미터를 사용하는 순서가
서로 반대라는 점이다.
_curry 함수는 a, b, c, d 순서로 파라미터를 사용하지만
_curry_right 함수는 d, c, b, a 처럼
역순으로 파라미터를 사용한다.

여기서 설명한 것처럼 파라미터를 lazy 하게 사용하는 방식을
함수형 프로그래밍에서는 '커링(currying)' 이라고 한다.

함수형 프로그래밍에서는 어떤 함수를 구현할때
가장 왼쪽 파라미터부터 하나씩 명시한 함수를 실행하는
left 버전 고차함수와
가장 오른쪽 파라미터부터 명시한 함수를 실행하는
right 버전 고차함수
두 가지로 구현하는 경우가 일반적이다.

_reduce 01
_reduce 02

_reduce 함수는
어떤 항목들의 집합에 속한 요소들에 대해서
일괄적으로 어떤 연산을 한 뒤
하나의 최종결과값을 반환하는 기능이다.
reduce 함수는 흔히 fold 라고도 부른다.

함수 오버로딩을 적용해서 두 가지 버전으로 구현했는데
accumulator 를 입력받는 버전과
집합의 첫번째 요소를 accumulator 로 사용하는 버전
두 가지로 구현했다.

이 _reduce 는
위의 _curry 와 마찬가지로
left 버전, right 버전 두 가지로 구현할 수도 있다.

여기서도 보면 _reduce 함수를 구현하기 위해서
_curry 함수를 사용하면서 레고 블럭 놀이를 하고 있다.

_pipe

_pipe 는 여러개의 함수 리스트를 받아서
순서대로 하나씩 적용하는 기능이다.
이렇게 파이프라인을 먼저 셋업해두고 나중에
파라미터를 넘기면서 실행하면
파이프라인에 지정한 모든 함수를 호출한 결과값을 얻을 수 있다.
이를 구현할때
파이프라인만 먼저 만들어두는 버전과
파이프라인을 만들면서 아규먼트까지 함께 전달해서
결과값을 즉시 얻는 버전
두 가지로 구현했다.

처음 사용자 정보 목록 구하는 기능 고차함수로 다시 구현

처음에 사용자 정보를 추출하는 두 개의 기능을
지금까지 알아본 다양한 고차함수의 특성을 바탕으로
다시 구현해보면 이렇게 된다.

기본적인 아이디어는
바로 앞에서 구현한 _pipe 함수를 사용하는 것이지만
생각보다 잘 되지 않아서
우선은 프로그래밍 언어에서 기본적으로 제공하는 기능을 사용해서
_pipe 를 사용했을때와 동일한 구조를 만들어봤다.

함수형 프로그래밍의 원리를 적용해서 구현하게 되면
코드가 선언적으로 변하고
유지보수하는 입장에서
한층 더 직관적으로 이해하기 쉬워지는 장점이 있다.

<참조>
온라인 강의, 자바스크립트로 알아보는 함수형 프로그래밍 (ES5), 유인동

profile
세상에 도움이 되고, 동료에게 도움이 되고, 나에게 도움이 되는 코드를 만들고 싶습니다.

0개의 댓글