[무료] 자바스크립트로 알아보는 함수형 프로그래밍 (ES5) - 인프런 | 강의

마플(http://www.marpple.com)의 CTO 유인동님이 알려주는 함수형 프로그래밍에 대한 강좌 입니다. 함수형 프로그래밍으로 라이브러리를 직접 만들어가며 함수형 프로그래밍의 패러다임과 코딩의 즐거

www.inflearn.com

/* 요소 리스트 값을 하나씩 꺼내어 준다.*/
function each(list, iter){
    const _keys = keys(list);
    for(let i=0;i<_keys.length;i++){
        iter(list[_keys[i]], _keys[i]);//값 뿐만이 아니라 키도 인자로 넘기도록 수정
    } 
    return list;
}
/* 수집하기 대표 요소를 변환시키는 함수*/
function map(list , mapper){
    const result = [];
    each(list, (val,key) => result.push(mapper(val,key)));//키도 넘김
    return result;
}

pairs

key:value 쌍으로하는 배열을 반환하는 함수

/*key:value 쌍으로하는 배열을 반환하는 함수*/
function pairs (list){
    return map(list, (val, key)=>[key,val]);
}
console.log(
    _.pairs(users[0]),
)

_.go(
    users,
    _.map(u=>_.pairs(u)),
    console.log
)

마무리

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
//순수함수 예시
function add(a,b){
    return a+b;
}
console.log(add(12,34));
console.log(add(12,34));

//부수효과 예시
let sideEffect1 = 10;
function add2(a,b){
    return add(a,b)+ sideEffect1;
}
console.log(add2(10,20));
sideEffect1 = 20;
console.log(add2(10,20));

let sideEffect2 = 10;
function add3(a,b){
    sideEffect1 = a; // 외부에 영향을 준다
    sideEffect2 = b;
    return add(a,b);
}
console.log(add3(10,20));
console.log(add3(10,20));

//새로운 값을 만든다는 것은 Call By Reference가 대상이다.
const arr = [1,2,3,4,5];
function multiplyArr(arr){
    //새로운 값
    const result = arr.slice();
    for(const idx in result){
        result[idx] = result[idx] * 2;
    }
    return result;
}
console.log(multiplyArr(arr));
console.log(arr);

console.clear();


//일급함수
const get10 = () => 10; //함수를 변수에 담는다.

//함수를 인자로 받는 함수, 함수를 변수에 담았음
const add30 = function(fn){
    return 30 + fn();
}
//인자로 함수를 전달했다.
console.log(add30(get10));


//함수는 단독으로 존재할 수 있다.
function hello(name){
    console.log("hello "+name);
}

//메서드는 클래스 속에 존재한다.
class Hello{
    hello(name){
        console.log("hello "+name);
    }
}
hello("홍길동");
new Hello().hello("홍길동");
console.clear();
</script>
<script>
const users = [
  { id: 10, name: 'ID', age: 36 },
  { id: 20, name: 'BJ', age: 32 },
  { id: 30, name: 'JM', age: 32 },
  { id: 40, name: 'PJ', age: 27 },
  { id: 50, name: 'HA', age: 25 },
  { id: 60, name: 'JE', age: 26 },
  { id: 70, name: 'JI', age: 31 },
  { id: 80, name: 'MP', age: 23 }
];

//함수를 담은 obj
const _ = (()=>{
    return {
        filter:curryr(filter) ,
        reject:curryr(reject) ,
        negate:negate ,
        compact:compact ,
        find : find,
        find_index : find_index,
        some : some,
        every : every,
        contains : contains,
        map:curryr(map) ,
        get:curryr(get) ,
        values:values,
        pluck:pluck,
        pairs:pairs,
        identity:identity,
        each: each,
        curry:curry,
        curryr:curryr,
        reduce: curryr(reduce),
        min: min,
        max: max,
        min_by: curryr(min_by),
        max_by: curryr(max_by),
        group_by: curryr(group_by),
        count_by: curryr(count_by),
        rest: rest,
        pipe: pipe,
        go: go,
    };

/*찾아내기 대표 함수 find
filter와 유사하나 predi에 걸러진 처음으로 만나는 요소를 반환한다.*/
function find(list, predi){
    const _keys = keys(list);
    for(let i=0;i<_keys.length;i++){
        const val = list[_keys[i]];
        if(predi(val)) return val;
    }
    return undefined; //생략 가능, 자동으로 undefined 리턴될 것
}
/*predi에 걸러진 처음으로 만나는 요소의 인덱스를 리턴한다.*/
function find_index(list, predi){
    const _keys = keys(list);
    for(let i=0;i<_keys.length;i++){
        if(predi(list[_keys[i]])) return i;
    }
    return -1; //자바스크립트 표준에 맞춤
}
/* find_index 특화 함수 주어진 조건에 값이 하나라도 존재하는지 검사한다.*/
function some(list, predi){
    //하나라도 해당하면 -1을 리턴할 수 없다.
    return find_index(list, predi||identity) !== -1;
}
/* find_index 특화 함수 주어진 조건에 값이 전부 일치하는지 검사한다.*/
function every(list, predi){
    //주어진 조건 반대에 해당하는 값을 못찾으면, 전부 해당하는 것
    return find_index(list, negate(predi||identity)) === -1;
}

function every2(list, predi){
    return find_index(list, function(val){
        return !predi(val);
    })
}
function every3(list, predi){
    return find_index(list, (val)=> !predi(val) )
}
/*주어진 값이 존재하는지 검사하는 함수*/
function contains(list, data){
    return find_index(list, val => val === data) !== -1;
}

/* 요소를 걸러내는 함수, predi라는 보조함수를 인자로 받는다. */
function filter(list, predi){
    //함수형 스타일은 기존 요소를 손상시키지 않고 항상 새로운 값을 리턴한다.
    const result = [];
    //보조함수를 인자로 받아
    each(list, val =>{
        if(predi(val)) result.push(val);
    });
    return result;
}

/*단순히 filter에 반대로 행동하는 함수
(== 주어진 조건에 해당하는 값을 반환하는 함수*/
function reject(list, predi){
    return filter(list, negate(predi) );
}

/*결과를 반대로 바꾸는 함수*/
function negate(predi){
    return val => !predi(val);
}

/*참으로 판단되는 값을 걸러내는 함수*/
function compact(list){
    return filter(list, identity);
}


/* 수집하기 대표 요소를 변환시키는 함수*/
function map(list , mapper){
    const result = [];
    each(list, (val,key) => result.push(mapper(val,key)));
    return result;
}
/*key:value 쌍으로하는 배열을 반환하는 함수*/
function pairs (list){
    return map(list, (val, key)=>[key,val]);
}

/*요소 값을 반환하는 함수*/
function values(list){
    return map(list,identity);
}
/*자기자신을 그대로 반환하는 함수*/
function identity(arg){
    return arg;
}

/*요소에서 특정 값만 추출하는 함수 */
function pluck(list, key){
    return map(list, obj => obj[key]);
}



/* 요소 리스트 값을 하나씩 꺼내어 준다.*/
function each(list, iter){
    const _keys = keys(list);
    for(let i=0;i<_keys.length;i++){
        iter(list[_keys[i]], _keys[i]);
    }
    return list;
}
/*안전하게 값 가져오기*/
function get (obj,key){
    return obj === null ? undefined : obj[key];
}
/* 객체의 keys를 안전하게 리턴하는 함수*/
function keys(obj){
    return is_object(obj) ? Object.keys(obj) : [];
}
/* 객체인지 안전하게 확인하는 함수 */
function is_object(obj){
    return typeof obj === "object" && !!obj;
}


/* 커링 함수 오직 함수만을 인자로 받아 함수를 리턴함, 평가 시점을 조율*/
function curry(fn){
    return function(a,b){
        return arguments.length === 2 ? fn(a,b) : function (b){
            return fn(a,b);
        }
    }
}
/* 단순히 인자 순서만 변경, 화살표 함수로만 표현 */
function curryr(fn){
    return function(a,b,c){
        if(arguments.length === 2) return fn(a,b);
        else if (arguments.length === 3) return fn(a,b,c);
        else return b => fn(b,a);
    };
}

/*요소를 하나로 접어가는 함수
list 요소, iter 보조함수, memo 초기값*/
function reduce(list, iter, memo){
    if(arguments.length===2){
        memo = list[0];
        list = rest(list);
    }
    each(list, val =>{
        //memo에 계속 누적하여 적용한다.
        memo = iter(memo, val);
    });
    return memo;
}

function min(list){
    return reduce(list, (a, b)=> a > b ? b : a);
}
function max(list){
    return reduce(list, (a, b)=> a < b ? b : a);
}
/*보조 함수를 인자로 받아, 그 반환값을 기준으로 평가한다.*/
function min_by(list,iter){
    return reduce(list, (a, b)=> iter(a) > iter(b) ? b : a);
}
function max_by(list,iter){
    return reduce(list, (a, b)=> iter(a) < iter(b) ? b : a);
}

/*주어진 조건으로 그룹핑한 결과를 반환하는 함수*/
function group_by(list,iter){
    return reduce(list, (group, val) => {
        //그룹화하려면 그룹 기준과 그룹화될 리스트 공간이 필요하다.
        //그룹 기준에 충족하는 첫 그룹화라면 빈 배열을 넣어준다.
        (group[iter(val)] = group[iter(val)] || [] ).push(val);
        return group;
    } ,{});
}
/*주어진 조건으로 그룹핑한 결과를 개수로 반환하는 함수*/
function count_by(list,iter){
    return reduce(list, (group, val) => {
        group[iter(val)] = group[iter(val)]+1 || 1;
        return group;
    } ,{});
}


/* 배열 첫 번째 요소를 잘라 나머지 배열을 리턴*/
function rest(list, num){
    return Array.prototype.slice.call(list,num||1);
}
/*리듀스 특화 함수, 함수만을 인자로 받는 함수*/
function pipe(){
    const fns = arguments;
    return function(val){
        return reduce(fns,function(val,fn){
            return fn(val);
        }, val );
    };
}

/*pipe 즉시 실행 버전*/
function go(data){
    //첫번째 데이터만 제외하면 함수 배열이다.
    const fns = rest(arguments);
    //함수 배열이기 때문에 arg1, arg2 ... argN 풀어주려면 apply를 사용한ㄷ.
    return pipe.apply(null,fns)(data);
}

})();

//나이 25세 이상 사용자 걸러내기
//절차지향 코드
const tmp = [];
for(const user of users){
    if(user.age>=25) tmp.push(user);
}
console.log(tmp);
//함수형 코드
console.log(
    _.filter(users, u=>u.age>=25)
);

console.log(
    _.filter(users, u=>u.age-(u.age%10) === 20), //20대만
    _.filter(users, u=>u.name.startsWith("J")) //J로 시작하는 user
);

//map 사용
console.log(
    _.map(users, u=>u.age) ,
    _.map(users, u=>u.name) ,
    _.map(users, u=>u.id) ,
);
_.each(users,u=>console.log(u));
//섞어쓰기, 30세 이상, user 이름
console.log(
    _.map(_.filter(users, u=> u.age >=30), u=>u.name)
    );
console.clear();

//중복이름으로 구분자 _
const _add = _.curryr((a,b)=>a+b);
const _add10 = _add(10);
console.log(_add);
console.log(_add10);
console.log(_add10(20));
console.log(_add(10,20));

const _sub = _.curryr((a,b)=>a-b);
const _sub10 = _sub(10);
console.log( _sub10(20) );

console.clear();

console.log(_.filter);
console.log(_.map);

const upperAge30 = _.filter(u=>u.age>=30);
const userName = _.map(u=>u.name);
console.log(upperAge30);
console.log(userName);

console.log(upperAge30(users));
console.log(userName(users));
console.log(userName(upperAge30(users)));

console.clear();
/*이전 reduce 실습 내용*/
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,0));
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,10));
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,-10));

console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val ));

//30세 이상 사용자 age 합
console.log(_.reduce(_.map(upperAge30(users),u=>u.age),(sum,val)=>sum+val ));
console.clear();
//30세 이상 사용자 age 합, pipe 사용
const tmpFilter = _.filter(u=>u.age>=30);
const tmpMap = _.map(u=>u.age);
const tmpReduce = _.reduce((sum,val)=>sum+val);

console.log(tmpFilter);
console.log(tmpMap);
console.log(tmpReduce);
console.clear();

_.pipe(
    tmpFilter,
    tmpMap,
    tmpReduce,
    console.log
)(users);

_.go(users,
    tmpFilter,
    tmpMap,
    tmpReduce,
    console.log
);


_.each([1,2,3,4], n=> console.log(n))
_.each(null, n=> console.log(n))
console.log( _.filter(null, n=> true));
console.log( _.map(null, n=> n));

console.clear();

_.each({a:1,b:2,c:3}, a=>console.log(a));
_.each([1,2,3], a=>console.log(a));

//Object.keys 동작방식
console.log(Object.keys(["a","b","c"]).join("-")  );
console.log(Object.keys("가나다라").join("-")   );
console.log(Object.keys({:"바",:"바",:"나"}).join("-")   );
//Object.values 동일
console.log(Object.values(["a","b","c"]).join("-")  );
console.log(Object.values("가나다라").join("-")   );
console.log(Object.values({:"바",:"바",:"나"}).join("-")   );

//Object.keys 는 for...in 과 유사
function forIn(list){
    const result = [];
    for(const arg in list) result.push(arg);
    return result.join("-");
}
//Object.values 는 for...of 과 유사
function forOf(list){
    const result = [];
    for(const arg of list) result.push(arg);
    return result.join("-");
}
console.log(forIn(["a","b","c"]) );
console.log(forIn("가나다라") );
console.log(forIn({:"바",:"바",:"나"}));
console.log(forOf(["a","b","c"]) );
console.log(forOf("가나다라") );
// console.log(forOf({고:"바",구:"바",마:"나"}));//불가능


//단일 접근 원칙
class Data{
    constructor(name, age){
        this._name = name;
        this._age =age;
    }
    get name(){
        console.log("name 게터 호출");
        return this._name;
    }
    get age(){
        console.log("age 게터 호출");
        return this._age;
    }
    set name(arg){
        console.log("name 세터 호출");
        this._name = arg;
    }
    set age(arg){
        console.log("age 세터 호출");
        this._age = arg;
    }
}
console.clear();
const data1 = new Data("홍길동", 50);
console.log(data1.name); //변수처럼 다룬다
data1.name = "임꺽정"
console.log(data1.name); //변수처럼 다룬다

console.clear();

console.log(
    _.values(users),
    _.values(users[0]),
);

console.log(
    _.pluck(users, "age"),
    _.pluck(users, "name"),
);

console.clear();


const filterEx = user=>user.age>30;

function ex(user){
    return filterEx;
}

console.log(
    _.filter(users, user=>user.age>30),
    _.reject(users, user=>user.age>30),
)
const testArr = [undefined, Infinity, null, NaN, 0, 1, 'true','false',true,false];
console.log(
    _.filter(_.identity)(testArr),
    _.filter(_.negate(_.identity))(testArr),
);
const compactEx = _.filter(_.identity);
console.log(
    compactEx(testArr),//이것과 완전히 같다.
    _.compact(testArr)
)
/*
compactEx
b => fn(b,a)   b는 _.filter  , a는 _.identity  , b로 testArr
_.compact
function compact(list){
    return filter(list, identity);
}
화살표 함수로 바꾸면
list => filter(list, identity)
기호만 바꾸면
b => fn(b,a)
*/

console.clear();

console.log(
    _.find(users, user => user.age >= 30),
    _.filter(users, user => user.age >= 30),
);
console.log(
    _.find_index(users, user => user.age <= 25),
    _.find_index(users, user => user.age > 40),
);
console.clear();

console.log(
    _.some(users, user=>user.age>30),
    _.every(users, user=>user.age>30),
    _.every(users, user=>user.age>10),
);
console.log(
    _.some(users),
    _.every(users),
    _.some(testArr),
    _.every(testArr),
);

console.log(
    _.contains(users, users[2] ),
    _.contains(users[2], "JM" ),
    _.contains(users[2], "홍길동" ),
);
console.clear();
const numArr = [-10,10,20,30,-20,-50,66];

console.log(_.min(numArr));
console.log(_.max(numArr));
console.log(_.min_by(users, user=>user.age));
console.log(_.max_by(users, _.get("age")));

console.clear();

_.go(
    users,
    _.group_by(user=>user.age-(user.age%10)),
    console.log
);
console.log(_.group_by(users,u=>u.age-(u.age%10)));
_.go(
    users,
    _.group_by(user=>user.age%2),
    console.log
);
_.go(
    users,
    _.count_by(user=>user.age-(user.age%10)),
    console.log
);
console.log(_.count_by(users,u=>u.age-(u.age%10)));
_.go(
    users,
    _.count_by(user=>user.age%2),
    console.log
);
console.clear();
console.log(
    _.pairs(users[0]),
)

_.go(
    users,
    _.map(u=>_.pairs(u)),
    console.log
)


</script>


</body>
</html>

function.html
0.01MB

 

+ Recent posts