-
반응형
정의
함수 란?
()
연산자를 적용하여 평가할 수 있는 모든 호출 가능 표현식- 호출시 사용 가능한 결과값을 반환
자, void 를 반환하는 함수를 우리는 앞으로 함수로 쳐주지 않기로 한다.
그럼 뭐라고 부르지?일급 시민
Javascript에서는 함수가 객체이므로 값으로 다룰 수 있어서 일급 또는 일급 시민 이라고 부른다.
보통 프로그래밍에서 일급이라고 함은, 적용 가능한 모든 연산을 수행할 수 있는 것들을 일급 이라고 부른다.(어렵다...?)그럼 그러한 연산은 뭐가 있을까?
- 값을 할당하거나
- 함수의 인자로 전달하거나
- 함수의 반환값으로 사용하거나
보통 위와 같은 케이스를 말한다.
그럼 Javascript에서 이러한 연산을 어떻게 수행하나 한번 보자.
먼저 값을 할당하거나
const add = function(a, b) { return a + b; }; // Or // const add = (a, b) => a + b; add(1, 2); // 3
함수의 인자로 전달하거나
function calculator(fn, a, b) { return fn(a, b); } calculator(add, 1, 2); // 3
함수의 반환값으로 사용하거나
function highOrderFn(fn) { return function(a, b) { return fn(a, b); }; } const join = highOrderFn(add); join('hello', 'world'); // helloworld
음, 일급 칭호를 받을 만 하군.
고차함수
바로 위 예제 코드 중
calculator
와highOrderFn
함수를 정의했는데 바로 이게 고차함수(High-order function) 이다.
즉, 함수를 인수로 전달받거나 반환받을 수 있다면 그게 바로 고차함수다.
고차함수, 고계함수 둘 다 같은 의미다.이 고차함수를 사용하면 과연 이득이 뭘까?
바로 함수를 전달하고, 반환받는 방식을 통해 굉장히 유연한 모듈성을 갖춘 코드를 작성할 수 있게 된다.역시 예제로 보는게 제일 좋겠다. 명령형 방식과 한번 비교해보자.
고객이 우리에게 요구사항을 전달했다.
미국 거주자 명단을 출력해주세요.
우리가 가지고 있는 데이터는 아래와 같다.
class Person { constructor(name, country) { this.name = name; this.country = country; } } const people = [ new Person('huna', 'Korea'), new Person('Martin Fowler', 'UK'), new Person('James Arthur Gosling', 'Canada'), new Person('Douglas Crockford', 'USA'), new Person('Yukihiro Matsumoto', 'Japan'), new Person('Ryan Dahl', 'USA') ];
명령형 방식을 먼저 보면
function printPeopleInTheUs(people) { for (let i = 0; i < people.length; i++) { const thisPerson = people[i]; if (thisPerson.country === 'USA') { console.log(thisPerson); } } } printPeopleInTheUs(people); // Person { name: 'Douglas Crockford', country: 'USA' } // Person { name: 'Ryan Dahl', country: 'USA' }
루프를 돌면서 값들을 하나씩 뽑아
USA
데이터를 출력하면 끝~이것도 나쁘지 않은데?
그렇다. 나쁘지 않다. 이대로 고객에게 전달해주자.
음, 생각이 바꼇어요. 다른 나라 거주자도 볼 수 있으면 좋겠어요.
자, 이제 고차함수를 통해서 좀 더 유연하게 바꿔보자.
고차함수를 사용할 수 있게 요구사항을 바꾼 고객에게 박수를 🤬function printPeople(people, action) { for (let i = 0; i < people.length; i++) { action(people[i]); } } function action(person) { if (person.country === 'USA') { console.log(person); } } printPeople(people, action); // Person { name: 'Douglas Crockford', country: 'USA' } // Person { name: 'Ryan Dahl', country: 'USA' }
고객의 요구사항에 맞게
action
함수를 인자로 변경해서 호출하면 된다.
근데 이정도로 바꾸려고 고차함수를 쓴다면 좀 허접하지 않는가?
일단 고객에게 보내보자.잠시 후...
저희는 콘솔 말고 파일에도 저장이 가능하면 좋겠어요
좀 더... 야근을 해보자...
function printPeople(people, selector, printer) { people.forEach(person => selector(person) && printer(person)); } const inUs = person => person.country === 'USA'; printPeople(people, inUs, console.log); // Person { name: 'Douglas Crockford', country: 'USA' } // Person { name: 'Ryan Dahl', country: 'USA' }
이제
printPeople
함수에데이터, 조건, 출력
순서로 인자를 전달받아 처리할 수 있도록 개선이 되었다.
고차함수를 통해 대충 함수 호출부분만 봐도 어떤 일을 하는지 짐작이 가지 않는가?다행히 고객도 만족스럽다고 연락이 왔다.
하지만 우리는 끝을 모르는 개발자.
리팩토링을 할 시간이다.이번 리팩토링에는 커링(currying) 이라는 FP의 꽃 기능을 소개하려고 한다.
자, 놀라지 마시라.
const curry = (fn, arity = fn.length, ...args) => arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args); const inCountry = curry((country, person) => person.country === country); people.filter(inCountry('USA')).map(console.log)
결과는 처음과 좀 다르지만 (Array.prototype.map() 함수는 (value, index, array) 를 인자로 받는다)
마지막 함수를 보면 체이닝을 통해 함수들을 아주 왕성하게 주고받는 코드를 볼 수 있을 것이다.또한
go
라는 함수를 통해 함수 합성으로 구현도 가능하다.
이 함수는 유인동 님의 강의에서 감명깊게 본 코드이다.
단,go
를 사용하기 위해선 Ramda와 같은 currying이 된 함수형 라이브러리가 필요하다.const go = (v, ...fn) => fn.reduce((a, f) => f(a), v); go( people, R.filter(inCountry('USA')), R.map(console.log) );
curry
,go
함수에 대해서는 나중에 따로 포스팅을 한번 할 예정이니 그때까지 천천히 코드를 음미해보길 바란다.출처: 함수형 자바스크립트
반응형'FP' 카테고리의 다른 글
How to make the Object as immutable in JS (0) 2020.07.09 FP vs OOP (0) 2020.07.08 Initial commit - FP (0) 2020.07.07