Web Front-end/함수형 프로그래밍(FP)

[JS/함수형] Go, Pipe

함수형 프로그래밍에서는 "Code"자체를 값으로 다룰 수 있게 된다. 

 

하지만, 코드를 "값"으로 표현하게 된다면, 중첩에, 중첩에 중첩이 되는 재귀적인 관계를 형성하게 되고, 나중에 사용하기에 번거롭고, 가독성도 매우 떨어진다. 따라서, 이 문제를 해결을 해야한다.

 

Go와 Pipe,Currying은 이러한 상황에서 제안된 아이디어들이다!

Go 

Go는 함수가 연속적으로 call 되는 구조를 띄고 있을 때, "Go"라는 하나의 함수로 축약하여, 나열하는 형식으로 구현하고, 즉시 그 값을 평가하자.

장점

  • 함수를 나열한다는 건, 가독성을 높일 수 있다 .
  • 값이 즉시 평가되기 때문에, 결과값을 바로 볼 수 있다.

축약이라는 말에서 알 수 있듯이, Go 함수는 내부적으로 Reduce를 이용하게 된다. 하지만, Go는 여러가지 논리를 조합해서 쓸 수 있게 된다. 아래의 코드를 보자!

go (
	0,
    a=>a+1,
    a=>a+10,
    a=>a+100,
    console.log,
)
//We want result :111

0이 지속적으로 뒤의 함수를 실행해서, 우리는 111이라는 값을 얻고싶다고 하자.

이럴 때, Go를 작성한다면 , 어떻게 만들 수 있을까 ? reduce는 아래와 같이 저번시간 처럼, 정의되어 있다.

const reduce = (f,acc,iter) => {
	if(!iter){
		iter = acc[Symbol.iterator]();
		acc = iter.next().value;
	}
	for (const a of iter){
		acc=f(acc,a)
	}
    return acc
}

Go

  • 구조분해를 통해서, 인자값들을 펼쳐서, 우선 인자를 넣어준다.
  • 이때, 인자들은 첫번째는 iterable(순회할 자료), 두번째 부터는 함수식이 들어갈거다.
  • 그렇다면, reduce에서, (a,f) =>f(a)를 보조함수로 던져주고, args(parameter) 자체를 주면 된다.
const go = (...args) => reduce((a,f) => f(a),args);

const test1=[
	{name:"1"},
    	{name:"2"},
    	{name:"3"},
]
go(
	test1,
    	console.log
)
//[{name:1},{name:2},{name:3}] Array가 출력될 것이다.

 

Go를 이용한다면, 아래와 같은 논리를 아주 쉽게, 설계할 수 있다.

  • 콘서트 티켓이 아래와 같이 있는데,  내가 만원 이하의 콘서트를 가고 싶다.
  • 그 콘서트의 이름을 출력해보자.
const Tickets =[
  {name:"IU의 팔레트",price:100000},
  {name:"PSY워터밤",price:90000},
  {name:"분신",price:55000},
  {name:"별될시간",price:45000},
  {name:"재롱잔치",price:3000},
  {name:"재롱잔치2",price:5000}
]

아마, reduce를 이용해서 아래와 같이 짤 수 있을거다. 

const add = (a,b) => a+b;
// 20000만원 이하 콘서트 이름 출력 
reduce(add,map(p=>p.name,filter(p=> p.price<=20000,products))))

But

  • 가독성이 매우 안좋고, 이해도 어렵다.
  • 차라리 그냥 for문을 써서, 구현하는게 더 가독성이 좋고, 유지보수 하기가 편할거다. 굳이 함수형을 쓸 이유가 없게된다. 
  • if문이 2중 ,3중으로되면 ,안좋듯이, 함수도 2중,3중이 되면 좋지않다

우리는 "Go"라는 걸 정의해줬고, 한번 활용해보자.

  • Reduce,map,filter는 정의되어있다고 가정한다.
go(
	Tickets,
    	Tickets =>filter(p=> p.price<=20000,Tickets),
    	//조건에 맞는 Ticket 걸러내고,
	Tickets => map(p=>p.name,Tickets),
    	//조건에 맞는 Ticket의 이름을 뽑고
    	names =>reduce(add,names),
    	//그 이름들을 합쳐준다.
    	console.log
    	//마지막에 출력해준다.
)

위와 같이, Go를 쓰게 되면, 함수의 전개를 나열할 수 있게 되고, 중첩된 걸 1차원적으로 표현이 가능해진다.

 

Pipe

Pipe는 Go함수와 다르게, 함수들의 Logic을 하나로 묶어서, 실행하는 합성된 함수를 나타내고 싶을 때, 이용하는 녀석이다. 함수를 리턴하는 함수로 구현을 해야한다.

그리고, Pipe Pattern은 , const f = Pipe(....f1, f2,f3...)로 여러가지 로직을 하나의 Function으로 묶자는 개념이므로, 

처음 Call시,  함수들을 받게 되고, 나중에 인자들을 받게 된다.

따라서, 함수를 분해해서, 우선 받고, call이 되면, go에 인자와 등록해놨던 함수들을 넘겨주기만 하면 된다.

 

Code

const pipe = (...fs) => (a) => go(a,...fs);

만약, 인자가 2개인 친구를 받고 싶다면, 아래와 같이 분해하는 방법도 있다! 

const pipe = (f,...fs) => (...as) =>go(f(...as),...fs)

const f = pipe(
  (a,b) => (a+b),
  a=>a+10,
  a=>a+100,
  a=>a+1000
);
console.log(f(0,2));
//1112

 

CodePen으로 TEST

See the Pen JS_gogo by Doge (@DogeIsFree) on CodePen.

 

Ref

이 글은 유인동님의 함수형 프로그래밍과 JavaScript ES6+를 보고 정리한 글입니다. 

https://www.inflearn.com/course/functional-es6/dashboard

 

함수형 프로그래밍과 JavaScript ES6+ - 인프런 | 강의

ES6+와 함수형 프로그래밍을 배울 수 있는 강의입니다. 이 강좌에서는 ES6+의 이터러블/이터레이터/제너레이터 프로토콜을 상세히 다루고 응용합니다. 이터러블을 기반으로한 함수형 프로그래밍,

www.inflearn.com