© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
📑
지연성 range와 느긋한 L.range 숫자를 받아 숫자 크기의 배열을 return하는 함수.
list를 출력하면 range는 배열을, L.range는 이터레이터를 출력함. L.range는 평가가 완벽히 되지 않은 상태를 유지(array를 만들지 않음)하다 유의미한 결과값이 필요할 때 평가함. 예를 들어 take나 reduce의 함수를 실행할 때 평가를 시작함. 콘솔 값이 같은 이유는 reduce가 이터러블을 받기 때문임. 성능 테스트 range는 1) array를 만들고, 2)이터레이터를 만들고 3)next로 순회. L.range는 1)이터레이터를 만들고, 2)이 이터러블은 자기자신을 return하는 이터러블이고, 3) 순회. take 지연성이 가진 효율성을 알아보자.
지연성을 가지는 값을 이터레이터로 만들게 되면 이터러블 프로토콜을 따르는 함수와의 조합성이 높음. 또한 range는 100개의 배열을 만들고 5개를 자르는 방식인 반면, L.range는 범위에서 5개의 값만을 만들기 때문에 효율적임. 심지어 L.range의 범위로 Infinity를 넣어도 가능함. Lmap 이터러블, 이터레이터 프로토콜을 따르면서 지연성을 가지는 map을 구현해보자.
지연성을 가지고 (평가를 미루고) 평가순서를 달리 조작할 수 있는 이터레이터를 반환하는 제너레이터 함수. Lmap 자체는 새로운 array를 만들지 않고 값마다 순회하며 yield 통해 함수에 적용된 값을 이터레이터의 next를 실행할 때마다 하나씩 전달하는 구조. 내가 원하는 방법으로 평가할 수 있음. ex) [it.next().value]
, [...it]
Lfilter yield가 배열 크기만큼 실행되는 것이 아닌 조건에 따라 먼저 필터되고 실행됨. Array.prototype.join 보다 다형성이 높은 join 함수 함수형 프로그래밍에서 join은 array가 아니어도 적용할 수 있음.
take, find find함수는 take를 이용해 결론을 만듦. filter일 경우 모든 경우를 순회하고 take를 처리하기 때문에 Lfilter를 사용함. L.flatten, flatten 값을 모두 펼쳐 하나의 배열을 만드는 함수. 지연적으로 동작함.
L.flatMap, flatMap 값을 모두 펼쳐 하나의 배열을 만드는 함수. 지연적으로 동작함.
함수형 프로그래밍 라이브러리 고차함수와 보조함수를 데이터에 적절하게 조합함 함수형 프로그래밍은 속도보단 안정성을 추구하는 방식임.
//range
const range = l => {
let i =0;
let res = [];
while(i < l) {
res.push(i);
i++;
}
return res;
}
const add = (a,b) => a+b;
console.log(range(5)); //[ 0, 1, 2, 3, 4 ]
var list = range(4);
console.log(reduce(add, list)); //6
//느긋한 range
const L = {};
L.range = function*(l) {
let i =0;
let res = [];
while(i < l) {
yield i;
i++;
}
}
console.log(L.range(5)); //[ 0, 1, 2, 3, 4 ]
var list = L.range(4);
console.log(reduce(add, list)); //6
function test(name, time, f) {
console.time(name);
while(time--) f();
console.timeEnd(name);
}
test('L.range',10,()=> reduce(add, L.range(1000000))); //L.range: 251ms
test('range',10,()=> reduce(add, range(1000000))); //range: 276ms
const take = (l, iter) => {
let res = [];
for(const a of iter) {
res.push(a);
if(res.length === l) return res;
}
}
console.log(take(5, range(100))); //[ 0, 1, 2, 3, 4 ]
console.log(take(5, L.range(100))); //같은 결과값 이지만 range 크기가 커질수록 효율적
Lmap = function *(f, iter) {
for(const a of iter) yield f(a);
};
var it = Lmap(a => a + 10, [1,2,3]);//아직 평가되지 않음.
console.log(it.next()); //{ value: 11, done: false }
console.log([...it]); //[12, 13]
Lfilter = function *(f, iter) {
for(const a of iter) if(f(a)) yield f(a);
};
const it = Lfilter( a => a%2, [1,2,3,4]);
console.log(it.next()); //{ value: 1, done: false }
const join = curry((sep = ',', iter) =>
reduce((a,b) => `${a}${sep}${b}`, iter));
function *a() {
yield 10;
yield 11;
yield 12;
yield 13;
}
console.log(a().join('-')); //error
console.log(join('-', a())); //10-11-12-13
const users = [
{age:32},
{age:31},
{age:37},
{age:28},
{age:25},
{age:32},
{age:31}
]
const find = (f, iter) => go(
iter,
Lfilter(f),
take(1),
([a]) => a
)
console.log(find(u => u.age < 30, users)); //{ age: 28 }
console.log([...[1,2],3,4,...[5,6],...[7,8,9]]) //[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
//원리
const isIterable = a => a && a[Symbol.iterator]; //null 방지 차원에서 &&사용.
Lflatten = function *(iter) {
for(const a of iter) {
if(isIterable(a)) for (const b of a) yield b; //추가 depts로 a 안에 있는 모든 b를 yield함.
else yield a;
}
};
var it = Lflatten([[1,2],3,4,[5,6],[7,8,9]]);
console.log(it.next()); //{ value: 1, done: false }
console.log(take(6, Lflatten([[1,2],3,4,[5,6],[7,8,9]]))); //[ 1, 2, 3, 4, 5, 6 ]
LflatMap = curry(pipe(Lmap, Lflatten));
var it = LflatMap(map(a => a*a), [[1,2],[3,4],[5,6],[7,8,9]]);
console.log([...it]); //[ 1, 4, 9, 16, 25, 36, 49, 64, 81 ]
export function* range(start = 0, stop = start, step = 1) {
if (arguments.length === 1) start = 0;
if (arguments.length < 3 && start > stop) step *= -1;
if (start < stop) {
while (start < stop) {
yield start;
start += step;
}
} else {
while (start > stop) {
yield start;
start += step;
}
}
}
export function map(f) {
return function* (iter) {
for (const a of iter) yield f(a);
}
}
export function filter(f) {
return function* (iter) {
for (const a of iter) if (f(a)) yield a;
}
}
export function take(limit) {
return function* (iter) {
for (const a of iter) {
yield a;
if (--limit === 0) break;
}
}
}
export function reduce(f) {
return function (acc, iter) {
if (!iter) acc = (iter = acc[Symbol.iterator]()).next().value;
for (const a of iter) acc = f(acc, a);
return acc;
}
}
export function each(f) {
return function (iter) {
for (const a of iter) f(a);
return iter;
}
}
export function go(arg, ...fs) {
return reduce((arg, f) => f(arg))(arg, fs);
}
export const head = ([a]) => a;
export const find = (f) => (iter) => head(filter(f)(iter));
export function inc(parent, k) {
parent[k] ? parent[k]++ : (parent[k] = 1);
return parent;
}
export const countBy = (f) => (iter) =>
reduce((counts, a) => inc(counts, f(a)))({}, iter);
export const identity = a => a;
export const count = countBy(identity);
export const groupBy = (f) => (iter) =>
reduce(
(group, a, k = f(a)) => ((group[k] = (group[k] || [])).push(a), group)
)({}, iter);
export function* entries(obj) {
for (const k in obj) yield [k, obj[k]];
}
export function* values(obj) {
for (const k in obj) yield obj[k];
}
export const isFlatable = a =>
a != null && !!a[Symbol.iterator] && typeof a !== 'string';
export function* flat(iter) {
for (const a of iter) isFlatable(a) ? yield* a : yield a;
}
export function zip(a) {
return function* (b) {
a = a[Symbol.iterator]();
b = b[Symbol.iterator]();
while (true) {
const { value, done } = a.next();
const { value: value2, done: done2 } = b.next();
if (done && done2) break;
yield [value, value2];
}
}
}
export function concat(...args) {
return flat(args);