TIL 1일차(함수형프로그래밍 지연평가 및 실습)

  • 지연평가(lodash 사용)
    • 지연평가를 시작하고 유지시키는 것
      1. map
      2. filter, reject
    • 지연평가를 끝내는 것
      1. take
      2. filter,reject
//Lodash는 차례차례 100번씩 실행하는 것이 아니라 하나씩 모든 로직을 타게된다
//즉 L.some의 값이 10보다 클때 함수가 종료되어 루프는 총 12번만 돔
  _.go(_.range(100),
      L.map(function (val) {
          ++mi;
          return val * val;
      }),
      L.filter(function (val) {
          ++fi;
          return val % 2;
      }),
      L.some(function (val) {
          return val > 10
      }),
      console.log);
			
  • 함수형 자바스크립트 요약

    • 함수는 최대한 작게만들기
    • 다형성이 높은 함수 만들기(타입이나 값체크를 하지말고 어떠한 형태의 값도 함수에 적용 가능)
      • javascript map, filter등은 함수가 아니라, 객체의 메소드이다.
      • 메소드는 해당클래스의 인스턴스에서만 작동 (map,filter는 array 에서만 작동)
      • jquery 객체 => array like 객체 , array가 아니라 map과 filter를 쓰지 못함.
    • 상태를 변경하지 않거나 정확하게 다루어 부수효과 최소
    • 동일한 인자를 받으면 동일한 결과를 만드는 순수함수 만들기
    • 복잡한 객체 하나 인자보다 일반적인 값 여러개 받기
    • 큰 로직을 고차함수로 만들고 세부 로직을 보조함수로 만들기
    • 모델이나 컬렉션 등의 커스텀 객체보다 기본객체 사용
    • 로직은 단방향
    • 작은 함수를 조합하여 큰 함수 만들기
    • 데이터 흐름 프로그래밍
    _go(users,
        _filter(function (list) {
            return list.age > 30;
        }),
        _map(_get('name')),
        console.log
    )
    
    • 함수형 프로그래밍 closure, elixir
    • 지연평가 + 동시성 + 병렬성
    • reduce와 결과는 동일하지만 fold를 사용하면 for문이 아니라 병렬로 접기 방식으로 아이디어를 낼 수 있다

  • 함수 실습

    var users = [
            {id: 101, name: 'ID'},
            {id: 102, name: 'BJ'},
            {id: 103, name: 'PJ'},
            {id: 104, name: 'HA'},
            {id: 105, name: 'JE'},
            {id: 106, name: 'JI'}
        ];
    
        var posts = [
            {id: 201, body: '내용1', user_id: 101},
            {id: 202, body: '내용2', user_id: 102},
            {id: 203, body: '내용3', user_id: 103},
            {id: 204, body: '내용4', user_id: 102},
            {id: 205, body: '내용5', user_id: 101},
        ];
    
        var comments = [
            {id: 301, body: '댓글1', user_id: 105, post_id: 201},
            {id: 302, body: '댓글2', user_id: 104, post_id: 201},
            {id: 303, body: '댓글3', user_id: 104, post_id: 202},
            {id: 304, body: '댓글4', user_id: 105, post_id: 203},
            {id: 305, body: '댓글5', user_id: 106, post_id: 203},
            {id: 306, body: '댓글6', user_id: 106, post_id: 204},
            {id: 307, body: '댓글7', user_id: 102, post_id: 205},
            {id: 308, body: '댓글8', user_id: 103, post_id: 204},
            {id: 309, body: '댓글9', user_id: 103, post_id: 202},
            {id: 310, body: '댓글10', user_id: 105, post_id: 201}
        ];
    
      1. 특정인의 posts의 모든 comments 거르기
    
        _go(
            // _filter(posts, function (post) {
            //     return post.user_id == 101
            // }),
            _.where(posts, {user_id:101}),
            // _.map(function(posts){
            //     return posts.id
            // }),
            _.pluck('id'),
            function (post_ids) {
                return _filter(comments, function (comment) {
                    return _.contains(post_ids, comment.post_id); 
    						//_.contains([1,2,3], 4); //false
                })
            },
            console.log
        )
    //간략화 하기
    var f1 = _.pipe(post_by, comments_by_posts);
    
    console.log(f1({user_id: '101'}));
    
      1. 특정인의 posts에 comments를 단 친구의 이름들 뽑기
    // 2. 특정인의 posts에 comments를 단 친구의 이름들 뽑기
        function post_by(attr) {
            return _.where(posts, attr)
        }
    
        var comments_by_posts = _.pipe(
            _.pluck('id'),
            function (post_ids) {
                return _.filter(comments, function (comment) {
                    return _.contains(post_ids, comment.post_id)
                })
            });
    
        _.go(
            {user_id: '101'},
            post_by,
            comments_by_posts,
            _.map(function (comment) {
                return _.find(users, function (user) {
                    return user.id == comment.user_id
                }).name
            }),
            _.uniq,
            console.log
        )
    
      1. 특정인의 posts에 comments를 단 친구들 카운트 정보
    _.go(
            {user_id: '101'},
            f1,
            comments_to_user_names,
            _.count_by,
            console.log
        )
    
      1. 특정인이 comment를 단 posts 거르기
    
        _.go(
            _.where(comments, {user_id: '105'}),
            _.pluck('post_id'),
            function (post_ids) {
                return _.filter(posts, function (post) {
                    return _.contains(post_ids, post.id)
                })
            },
            console.log
        )
    
      1. users + posts + comments (index_by와 group_by로 효율 높이기)
    var users2 = _.index_by(users, 'id'); //1:1로 매핑되는 경우 사용 가능 id가 객체의 키가됨
    
        //기존에 user 전체를 순환하는 구조에서 key 값으로 원하는 값만 가져
        function find_user_by_id(user_id) {
            return users2[user_id];
            // return _.find(users, function(user){
            //     return user.id==user_id
            // })
        }
    
        //comment + user + post_id 그룹(post_id별 데이터 그룹)
        var comments2 = _.go(comments,
            _.map(function (comment) {
                return _.extend({
                        user: find_user_by_id(comment.user_id)
                    },
                    comment)
            }),
            _.group_by('post_id') //post_id를 기준으로 comment 그룹화
        )
    
        var post2 = _.go(
            posts,
            _.map(function (post) {
                return _.extend({
                        // comment: _.filter(comments2, function (comment) {
                        //     return post.id == comment.post_id
                        // }),
                        comment: comments2[post.id],
                        user: find_user_by_id(post.user_id)
                    },
                    post)
            })
        );
    
        var post3 = _.group_by(post2, 'user_id');
    
        //user2에 위에만든 post3 넣음
        //원본은 절대로 건드리지 않는
        var users3 = _.map(users2, function (user) {
            return _.extend({
                // post: _.filter(post3, function(post){
                //     return post.user_id==user.id
                // })
                post: post3[user.id] || []
            }, user)
        })
    
        console.clear();
        // 5.1. 특정인의 posts의 모든 comments 거르기
        var user= users3[0];
        _.go(
            user.post,
            _.pluck('comment'),
            _.flatten, //배열합치기(납작하게만들다 )
            console.log
        )
    
        // 5.2. 특정인의 posts에 comments를 단 친구의 이름들 뽑기
        _.go(
            user.post,
            _.pluck('comment'),
            _.flatten, //배열합치기(납작하게만들다 )
            _.pluck('user'),
            _.pluck('name'),
            _.uniq,
            console.log
        )
    
        // 5.3. 특정인의 posts에 comments를 단 친구들 카운트 정보
        _.go(
            user.post,
            _.pluck('comment'),
            _.flatten, //배열합치기(납작하게만들다 )
            _.pluck('user'),
            _.pluck('name'),
            _.count_by,
            console.log
        )
    
        // 5.4. 특정인이 comment를 단 posts 거르기
        console.log(_.filter(post2, function (post) {
            console.log(post);
            return _.find(post.comment, function (comment) {
                return comment.user_id == 105
            })
        }));
    
    • reduce를 활용한 함수형 프로그래밍 실전
    var products = [
            {
                is_selected: true, // <--- 장바구니에서 체크 박스 선택
                name: "반팔티",
                price: 10000, // <--- 기본 가격
                sizes: [ // <---- 장바구니에 담은 동일 상품의 사이즈 별 수량과 가격
                    {name: "L", quantity: 4, price: 0},
                    {name: "XL", quantity: 2, price: 0},
                    {name: "2XL", quantity: 3, price: 2000}, // <-- 옵션의 추가 가격
                ]
            },
            {
                is_selected: true,
                name: "후드티",
                price: 21000,
                sizes: [
                    {name: "L", quantity: 2, price: -1000},
                    {name: "2XL", quantity: 4, price: 2000},
                ]
            },
            {
                is_selected: false,
                name: "맨투맨",
                price: 16000,
                sizes: [
                    {name: "L", quantity: 10, price: 0}
                ]
            }
        ];
    
        //1. 모든 수량
        var total_quantity =_.reduce(function (tq, product) {
          return _.reduce(product.sizes, function (tq, size) {
            return tq + size.quantity;
          },  tq)  //size가 중첩되어있어서 reduce 중첩, webstorm에서 tq를 클릭하면 어디서 사용하는 변수인지 확인 가능
        }, 0);
    
        var total_price =_.reduce(function (tp, product) {
          return _.reduce(product.sizes, function (tp, size) {
            return tp + size.quantity*(product.price+ size.price);
          },  tp)  //size가 중첩되어있어서 reduce 중첩, webstorm에서 tp를 클릭하면 어디서 사용하는 변수인지 확인 가능
        }, 0);
    
        _.go(
            products,
            total_quantity,
            console.log
        )
        //2. 선택 된 총 수량
        _.go(
                products,
                // _.filter(product=>product.is_selected),
                _.filter(_get('is_selected')),
                total_quantity,
                console.log
        )
        //3. 모든 가격
        _.go(
                products,
                // _.filter(product=>product.is_selected),
                total_price,
                console.log
        )
    
        //4. 선택 된 총 가격
        _.go(
                products,
                // _.filter(product=>product.is_selected),
                _.filter(_get('is_selected')),
                total_price,
                console.log
        )
    
    • 함수형 프로그래밍을 활용한 비동기 제어
    function square(a) {
            return new Promise(function (resolve) {
                setTimeout(function () {
                    return resolve(a * a);
                }, 500)
            })
        }
    
        square(10)
            .then(square)
            .then(square)
            .then(function (res) {
                console.log(res);
            });
        _.go(
            square(10),
            square,
            square,
            console.log
        )
    
        var list = [2, 3, 4];
        new Promise(function (resolve) {
            (function recur(res) {
                if (res.length == list.length) {
                    return resolve(res);
                }
                square(list[res.length]).then(function (val) {
                  res.push(val);
                  recur(res);
                });
            })([]) //빈 배열로 처음 recur 실행
        }).then(console.log);
    
        _.go(
                list,
                _.map(square),
                _.map(square),
                console.log
        )
    

댓글을 작성해보세요.

채널톡 아이콘