함수형 프로그래밍 시리즈 내용으로 계속 이어서 내용이 진행되므로 처음 부터 포스팅을 확인해주세요.
함수형 프로그래밍이 대체하고자 하는 것은 특정 패러다임이 아니라 언어 자체를 대체한다는 것을 중점적으로 생각해야 한다. 그렇기에 명령형으로 짜여진 코드의 틀(ex: 클래스)안에서도 얼마든지 다양한 로직들에 대하여서 함수형 프로그래밍의 코드로 대체하여 사용되어 질 수 있습니다.
이번 포스팅에서 자바스크립트의 기본 객체가 아닌 추가적으로 만들어낸 사용자 정의 내장 객체도 이터러블 프로그래밍으로 다루는 방법에 대하여 알아봅시다.
먼저 Map을 알아보자.
let m = new Map();
m.set('a', 1);
m.set('b', 2);
m.set('c', 3);
위와 같은 Map도 이터러블로 프로그래밍 하고 다시 새로운 Map으로 생성이 가능하다.
go(
m,
filter(([k, v]) => v % 2),
entries => new Map(entries),
console.log);
// Map(2) {"a" => 1, "c" => 3}
이어서 Set같은 경우도 마찬가지이다.
let s = new Set();
s.add(10);
s.add(20);
s.add(30);
console.log([...s]); // [10, 20, 30]
const add = (a, b) => a + b;
console.log(reduce(add, s)); // 60
Model과 Collection 클래스를 만들어서 사용자 정의 객체를 이터러블 프로그래밍과 함께 다루어 보자.
먼저, 다음과 같이 동작하는 Model과 Collection 클래스가 있다.
class Model{
constructor(attrs = {}){
this._attrs = attrs;
}
get(k){
return this._attrs[k];
}
set(k, v){
this._attrs[k] = v;
return this;
}
}
class Collection{
constructor(models = []){
this._models = models;
}
at(idx){
return this._models[idx];
}
add(model){
this._models.push(model);
return this;
}
}
이 Model과 Collection을 일반적인 객체지향적 설계로 사용해보자.
일단 아래 코드처럼 coll을 통해 새로운 모델 3개를 생성했다.
const coll = new Collection();
coll.add(new Model({ id: 1, name: 'AA'}));
coll.add(new Model({ id: 3, name: 'BB'}));
coll.add(new Model({ id: 5, name: 'CC'}));
이를 일반적으로 불러올 경우는 아래처럼 한다.
console.log(coll.at(2).get('name')); // CC
console.log(coll.at(1).get('id')); // 3
하지만, 위 코드는 생성된 모델배열을 순회하는 코드를 짜려면 아래처럼 range처럼 범위를 직접 지정해주어야 가능하다.
go(
L.range(3),
L.map(i => coll.at(i)),
L.map(m => m.get('name')),
each(console.log)); // AA BB CC
위와 같이 만들어 지는 것은, range라는 범위 숫자에 의존해야하며, 뒤떨어진 코드가 된다.
이를 이터러블 측면에서 순회되는 코드로 한번 바꿔보자.
Collection 클래스에 Symbol.iterator가 Model전체를 뽑아주는 역할을 해주게끔 하면 된다.
class Collection{
constructor(models = []){
this._models = models;
}
at(idx){
return this._models[idx];
}
add(model){
this._models.push(model);
return this;
}
*[Symbol.iterator](){
yield *this._models;
}
/*위와 같은 동작
[Symbol.iterator](){
return this._models[Symbol.iterator]();
}
*/
}
위와 같이 짜준다면 coll 을통해 이터러블 순회하면서, 동작을 할 수 있다.
go(
coll,
L.map(m => m.get('name')),
each(console.log)); // AA BB CC
/* 소문자로 id값 변화*/
go(
coll,
each(m => m.set('name', m.get('name').toLowerCase())));
go(
coll,
L.map(m => m.get('name')),
each(console.log)); // aa bb cc
이번엔 Model, Colletion 클래스를 상속받은 Product와 Products 클래스를 만들고 그에 어울리는 메소드를 만들며 어떻게 이터러블 프로그래밍과 객체지향 프로그래밍이 조화를 이룰 수 있는지 알아볼 것 입니다.
class Product extends Model{}
class Products extends Collection{}
각각 상속받은 Product와 Products가 있다. 이 코드에서 상품 몇개를 추가하고 값을 더하는 일반적인 객체지향 코드를 만들어 보자.
class Product extends Model{}
class Products extends Collection{
totalPrice() {
let total = 0;
this._models.forEach(product =>{
total += product.get('price');
});
return total;
}
}
const products = new Products();
products.add(new Product({ id: 1, price: 10000}));
products.add(new Product({ id: 3, price: 25000}));
products.add(new Product({ id: 5, price: 35000}));
console.log(products.totalPrice()); // 70000
이를 이터러블 프로그래밍을 적용하여 간결하게 해보자.
Products에서 this를 통해 이터러블이 순회할 수 있는지 확인해보면 다음과 같다.
class Products extends Collection{
totalPrice() {
console.log([...this]); // (3) [Product, Product, Product]
let total = 0;
this._models.forEach(product =>{
total += product.get('price');
});
return total;
}
}
위 Collection에서 Symbol.iterator를 구성했기 때문에 가능한 것인데, 이를 토대로 아래처럼 이터러블 프로그래밍을 적용하여 구현할 수 있다.
const addAll = reduce(add);
class Product extends Model{}
class Products extends Collection{
getPrices() {
return L.map(p => p.get('price'), this);
}
totalPrice() {
return addAll(this.getPrices());
}
}
const products = new Products();
products.add(new Product({ id: 1, price: 10000}));
products.add(new Product({ id: 3, price: 25000}));
products.add(new Product({ id: 5, price: 35000}));
console.log(products.totalPrice()); // 70000
이렇게 객체지향과 함수형 프로그래밍을 혼합하여 가시적인 코드들을 작성할 수 있게 되었다.