김용우
수강평 작성수
1
평균평점
5.0
게시글
블로그
전체 6
2021. 07. 03.
0
til-6 refactoring chapter2
chapter2: 리팩터링 원칙 리팩토링 겉보디 동작은 유지한 채 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법 리팩토링 하는 이유 코드설계가 좋아진다 a. 중복코드를 제거하여 모든 코드가 고유한 일을 수행하게 보장 소프트웨어 이해가 쉬어진다 a. 컴퓨터에게 시키려는 일과 이를 표현한 코드의 차이를 최대한 줄여야함 버그를 쉽게 찾을 수 있다 a. 리팩토링 과정을 통해 코드가 하는일을 깊이있게 파악하며 새로 깨닫은걸 코드에 반영한다 b. 뛰어난 프로그래머보다 단지 좋은 습관을 지닌 괜찮은 프로그래머 프로그래밍 속도를 높일 수 있다 a. 기능이 누적될 수록 걸리는 시간이 배로 증가하지 않는다 언제 리팩토링을 할까? 처음에는 그냥한다 비슷한 일을 두번째로하면 일단 계속 진행한다 비슷한 일을 세번째 하게되면 리팩토링 한다 준비를 위한 리팩토링: 기능을 쉽게 추가한다 이해를 위한 리팩토링: 코드를 이해하기 쉽게 만든다 쓰레기 줍는 리팩토링 계획된 리팩토링과 수시로 하는 리팩토링 a. 무언가 수정하려 할때는 먼저 수정하기 쉽게 정돈하고 그런 다음 쉽게 정돊자 b. 보기 싫은 코드를 발견하면 리팩토링하자. 잘 작성된 코드 역시 수많은 리팩토링을 거쳐야한다 리팩터링 시 고려할 문제 새 기능 개발 속도 저하 코드소유권 브랜치 테스팅 레거시 코드 테스트 주도 개발 : 자가 테스트 코드와 리팩터링을 묶음

2021. 07. 01.
0
til-5 refactoring study
함수 추줄하기 및 변수 인라인, 함수 옮기기 다형성을 통한 리팩토링 실습 // import playData from "./plays";// import invoiceData from "./invoices";const plays = { 'hamlet': {'name': 'Hamlet', 'type': 'tragedy'}, 'as-like': {'name': 'As You Like it', 'type': 'comedy'}, 'othello': {'name': 'Othello', 'type': 'tragedy'}}const invoices = { 'customer': 'BigCo', 'performances': [ { 'playID': 'hamlet', 'audience': 55 }, { 'playID': 'as-like', 'audience': 35 }, { 'playID': 'othello', 'audience': 40 }, ] }class PerformanceCalculator { constructor(aPerformance, aPlay) { this.performance = aPerformance; this.play = aPlay; } get amount() { throw new Error('서브클래스에서 처리하도록 설계되었습니다'); } get volumeCredits() { return Math.max(this.performance.audience - 30, 0); }}class TragedyCalculator extends PerformanceCalculator { get amount() { let result = 40000; if (this.performance.audience > 30) { result += 1000 * (this.performance.audience - 30) } return result; }}class ComedyCalculator extends PerformanceCalculator { get amount() { let result = 30000; //변수 초기화 if (this.performance.audience > 20) { result += 10000 + 500 * (this.performance.audience - 20) } result += 300 * this.performance.audience; return result; } get volumeCredits() { return super.volumeCredits+Math.floor(this.performance.audience / 5); }}function createPerformanceCalculator(aPerformance, aPlay) { switch(aPlay.type){ case "tragedy": return new TragedyCalculator(aPerformance, aPlay); case "comedy": return new ComedyCalculator(aPerformance, aPlay); } return new PerformanceCalculator(aPerformance, aPlay);}console.log(statement(invoices, plays));// console.log(htmlStatement(invoices, plays));function usd(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2 }).format(aNumber / 100);}function statement(invoice, plays) { return renderPlainText(createStatementData(invoice, plays));}function htmlStatement(invoice, plays) { return renderHtml(createStatementData(invoice, plays))}function renderHtml(data) { let result = `Statement for ${data.customer}\n`; result += "\n"; result += "playseatscost"; for (let perf of data.performances) { result += ` ${perf.play.name}${perf.audience}`; result += `${usd(perf.amount)}\n`; } result += "\n"; result += `Amount owed is ${usd(data.totalAmount)}\n`; result += `You earned ${data.totalVolumeCredits} credits\n`; return result;}function createStatementData(invoice, plays) { const statementData = {}; statementData.customer = invoice.customer; statementData.performances = invoice.performances.map(enrichPerformance); statementData.totalAmount = totalAmount(statementData); statementData.totalVolumeCredits = totalVolumeCredits(statementData) // return renderPlainText(statementData, invoice, plays); return statementData; function enrichPerformance(aPerformance) { const calculator = createPerformanceCalculator(aPerformance, playFor(aPerformance)); //공연료 계산기 생성 const result = Object.assign({}, aPerformance); //얕은복사 result.play = calculator.play; //class play() 함수 호출 result.amount = calculator.amount; //class amount() 함수 호출 result.volumeCredits = calculator.volumeCredits; return result; } function playFor(aPerformance) { return plays[aPerformance.playID]; } function amountFor(aPerformance) { //값이 바뀌지 않는 매개 변수 전달 return new PerformanceCalculator(aPerformance, playFor(aPerformance)).amount; } function volumeCreditsFor(aPerformance) { let volumeCredits = 0; volumeCredits += Math.max(aPerformance.audience - 30, 0); //희극 관객 5명마다 추가 포인트를 제공한다 if ('comedy' === aPerformance.play.type) volumeCredits += Math.floor(aPerformance.audience / 5); return volumeCredits; } function totalAmount(data) { // for (let perf of data.performances) { // //포인트를 적립한다 // result += perf.amount // } return data.performances.reduce((total, p) => total + p.amount, 0) } function totalVolumeCredits(data) { // for (let perf of data.performances) { // //포인트를 적립한다 // result += perf.volumeCredits // } return data.performances.reduce((total, p) => total + p.volumeCredits, 0) }}function renderPlainText(data, invoice, plays) { let result = `청구내역( 고객명: ${data.customer})\n`; for (let perf of data.performances) { //청구 내역을 출시한다 result += `${perf.play.name}: ${usd(perf.amount)} (${perf.audience}석) \n`; } result += `총액: ${usd(data.totalAmount)}\n`; result += `적립 포인트: ${data.totalVolumeCredits}점\n`; return result; function usd(aNumber) { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", minimumFractionDigits: 2 }).format(aNumber / 100); }}

2021. 06. 30.
0
Til-4일차 이터러블 프로토콜을 사용한 map, reduce, filter 함수생성
이터러블 map함수 생성을 사용한 중복제거 및 유사배열 처리 const products = [ {name: '반팔티', price: 15000}, {name: '긴팔티', price: 20000}, {name: '핸드폰케이스', price: 15000}, {name: '후드티', price: 30000}, {name: '바지', price: 25000} ]; const map = (f, iter)=>{ let res=[]; for(const a of iter){ res.push(f(a)) } return res; } //name, price 속성 값을 제외하곤 중복임 let names=[]; for(const a of products){ names.push(a.name); } console.log(names); // console.log(map(p=>p.name, products)); console.log(map(p=>p.price, products)); //이터러블 프로토콜을 사용한 map의 다형성 document.querySelectorAll('*').map(el=> el.nodeName); // 유사배열로 map 사용 불가 //우리가 만든 map함수는 유사배열도 처리가능 map(el=> el.nodeName, document.querySelectorAll('*'));- 이터러블 프로토콜을 따르면 모두 map 사용 가능 const l = document.querySelectorAll('*')[Symbol.iterator](); l //Array Iterator {} l.next(); //{value: html, done: false} l.next(); //{value: head, done: false} //gen을 활용해 사실상 모든로직은 map을 사용가능 function *gen(){ yield 2; yield 3; yield 4; } console.log(map(a => a * a, gen())); //4,9,16 이터러블 프로토콜을 활용한 map(key,value) 예제 let m = new Map(); m.set('a',10); m.set('b',10); m.set('c',10); console.log(map(([k, v]) => [k, v * v], m)); //[ [ 'a', 100 ], [ 'b', 100 ], [ 'c', 100 ] ] 이터러블 프로토콜을 활용한 filter 함수 생성 const products = [ {name: '반팔티', price: 15000}, {name: '긴팔티', price: 20000}, {name: '핸드폰케이스', price: 15000}, {name: '후드티', price: 30000}, {name: '바지', price: 25000} ]; let under20000 = []; for(const a of products){ a.price a.price a.price > 20000, products)); console.log(filter(a => a % 2, [1, 2, 3, 4])); console.log(filter(a => a % 2, function* () { yield 1; yield 2; yield 3; yield 4; }())); Reduce 함수 생성을 통한 이터러블 프로토콜 제어 const nums = [1,2,3,4,5]; let total =0; //기존 reduce(접기방식) for(const a of nums){ total+=a; } console.log(total); function reduce(f, acc, iter){ if(!iter){ //iter이 없다면 첫번째 요소를 0 번째로 세팅 iter= acc[Symbol.iterator](); acc=iter.next().value; } for(const a of iter){ acc=f(acc,a) } return acc; } function add(a,b){ return a+b; } console.log(reduce(add, [1,2,3,4,5])); const products = [ {name: '반팔티', price: 15000}, {name: '긴팔티', price: 20000}, {name: '핸드폰케이스', price: 15000}, {name: '후드티', price: 30000}, {name: '바지', price: 25000} ]; console.log(reduce((total,product) =>total+product.price, 0, products));

2021. 06. 30.
1
Til-3 -일급함수, 이터러블/이터레이터
평가 코드가 계산(Evaluation) 되어 값을 만드는 것 일급함수: 함수가 값으로 다뤄질 수 있다 값으로 다룰 수 있다 변수에 담을 수 있다 함수의 인자로 사용가능 함수의 결과로 사용가능 const a=10; const add10 = a=> a+10 const r =add10(10); console.log(r); //일급함수 이기에 아래와 같이 사용가능 var f1= ()=>()=>1; console.log(f1()); //()=>1 f1()(); //1 (f1()); //()=>1 고차함수: 함수를 값으로 다루는 함수 a. 함수를 인자로 받아서 실행하는 함수 const apply1 = f => f(1); const add2 = a => a + 2; log(apply1(add2)); log(apply1(a => a - 1)); const times = (f, n) => { let i = -1; while (++i log(a + 10), 3); b. 함수를 만들어 리턴하는 함수 const addMaker = a => b => a + b; const add10 = addMaker(10); log(add10(5)); log(add10(10)); es6에서의 리스트 순회(for of , for in) const list = [1, 2, 3]; for (var i = 0; i 이터러블/이터레이터 프로토콜 a. 이터러블: 이터레이터를 리턴하는 Symbol.iterator 를 가진 값 b. 이터레이터: { value, done } 객체를 리턴하는 next() 를 가진 값 c. 이터러블/이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록한 규약 ### Array를 통해 알아보기 log('Arr -----------'); const arr= [1, 2, 3]; let iter1=arr[Symbol.iterator](); //arr: 이터러블, iter1: 이터레이터 for (const a of iter1)log(a); //iter1.next(); 입력할때마다 다음값 출력 ### Set을 통해 알아보기 log('Set -----------'); const set= new Set([1, 2, 3]); for (const a of set)log(a); ### Map을 통해 알아보기 log('Map -----------'); const map= new Map([['a', 1], ['b', 2], ['c', 3]]); for (const a of map.keys())log(a); //키 정보만 가져옴 for (const a of map.values())log(a); for (const a of map.entries())log(a); console.clear(); 사용자 정의 이터러블/이터레이터 구현 const iterable = { [Symbol.iterator]() { **//동적인 값을 key로 사용하기 위한 메소드 생성방법(이해X)** let i = 3; return { next(){ return i==0? {done:true}: {value:i--, done:false} }, **[Symbol.iterator](){return this}** //이걸로 인해 iterator for of 문이 가능하고 next 후 중간부터도 시작 가능함 } } } var iterator= iterable[Symbol.iterator](); //메소드 실행 console.log(iterator.next()); //{ value: 3, done: false } console.1log(iterator.next()); //{ value: 2, done: false } console.log(iterator.next()); //{ value: 1, done: false } for(a of iterable){ console.log(a); //3,2,1 } for(a of iterator){ // [Symbol.iterator](){return this} 때문에 실행 가능 console.log(a); //3,2,1 } //기존 iterable은 next를 사용해서 중간부터 시작할 수 있음 const arr2= [1,2,3]; let iter2= arr2[Symbol.iterator](); iter2.next(); //한번 호출 했음으로 2,3 출력 for(a of iter2){ console.log(a); } 최근 array, map, set 내장 객체 뿐만 아니라 오픈 소스및 브라우저 web api 에서도 이터러블/이터레이터를 사용 할 수 있음 for(a of document.querySelectorAll('*')){ console.log(a) }; 전개연산자 역시 이터러블/이터레이터를 이용한 함수 console.clear(); const a = [1, 2]; // a[Symbol.iterator] = null; //주석풀면 전개연산자 사용 불가 log([...a, ...arr, ...set, ...map.keys()]); 제너레이터와 이터레이터 제너레이터: well-formed 이터레이터를 리턴하는 함수 자바스크립트에서 iterator이면 for of 로 순회 할 수 있다 즉 제너레이터 함수 내부 문장을 사용하여 어느 값이나 상태를 순회 할 수 있다 function *gen(){ yield 1; yield 2; yield 3; return 100; } let iter =gen(); console.log(iter == iter[Symbol.iterator]()); //well-formed이터레이터 console.log(iter.next()); console.log(iter.next()); console.log(iter.next()); console.log(iter.next()); //{ value: 1, done: false } //{ value: 2, done: false } //{ value: 3, done: false } **//{ value: 100, done: true }** for(let a of gen()) console.log(a); //1,2,3 (리턴값은 안나옴) 제너레이터 실습 function *odd(l){ for(let i=0;i for...of, 전개 연산자, 구조 분해, 나머지 연산자 이터레이터 활용 console.log(...odd(10)); //1~9 console.log([...odd(10), ...odd(20)]); //[1~9, 1~19] const [head , ...tail] = odd(5); console.log(head); //1 console.log(tail); //[3,5]

2021. 06. 28.
0
Til-2 함수와 프로토타입 체이닝
리터럴 : 표기법 a. 객체 리터럴: 객체를 생성하는 표기법 b. 함수 리터럴: 함수를 생성하는 표기법 var foo={name:'foo', age:30, gender: 'male}; //객체리터럴 function add(x,y){ //함수 리터럴 return x+y; } 함수: javascript에서 함수도 일반 객체처럼 값으로 취급 함수표현식 a. 변수에 함수를 값으로 할당, 보통 익명함수를 할당함 b. 기명함수를 할당해도 기명으로 사용할 수 없다 var add= function(x,y){ //add: 함수변수 return x+y; } var add= function sum(x,y){ return x+y; } //위 add 함수는 스크립트 엔진이 아래와 같이 변경시킴 var add= function **add**(x,y){ return x+y; } add(3,4) //7 sum(5,6) //에러 발생 , 기명함수 값 사용 불가 익명함수: 이름이 없는 함수형태 함수 호이스팅 발생원인: 스크립트의 변수 생성과 초기화 작업이 분리돼서 진행됨 함수 선언문 형태로 정의한 함수의 유효범위는 코드의 맨 처음부터 시작함 호이스팅 문제로 함수 선언문을 사용 안하는 걸 추천하는 사람도 있음 add(2,3); //5 function add(a,b){ return a+b; }; add(3,4); //7 add 함수코드는 함수 객체 add의 [code] 프로퍼티에 저장됨 함수는 일급객체 a. 변수나 배열의 요소에 할당가능 b. 함수의 인자로 전달 가능 c. 함수의 리턴값으로 사용 가능 d. 동적으로 프로퍼티생성가능 var foo = function(){ return function(){ console.log("함수에함수"); } } var baz = foo(); baz(); Function.prototype 객체: 모든 함수 객체의 부모역할을 하는 프로토타입객체(—proto—) a. constructor 프로퍼티 b. toString 메서드 c. apply ,call , bind 프로퍼티 prototype 프로퍼티 모든 함수는 객체로서 prototype 프로퍼티를 가지고 있다 부모를 나타내는 —prototype—와 혼동하면 안됨 함수 객체와 프로토타입 객체와의 관계 아래와 같이 서로 밀접하게 연결되어있다 함수 객체의 프로토타입의 생성자는 매핑된 함수를 가리킴(myFunction) myFunction(함수객체) myFunction.prototype(프로토타입 객체) prototype 프로퍼티 -----> 콜백함수: 어떤 이벤트가 발생했거나 특정시점에 도달했을때 시스템에서 호출되는 함수 ex) dom에서 이벤트 클릭시 함수실행 즉시 실행 함수 (function (name){ console.log('my name is ', name) })('foo');

2021. 06. 27.
0
TIL 1일차(함수형프로그래밍 지연평가 및 실습)
지연평가(lodash 사용) 지연평가를 시작하고 유지시키는 것 map filter, reject 지연평가를 끝내는 것 take 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} ]; 특정인의 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'})); 특정인의 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 ) 특정인의 posts에 comments를 단 친구들 카운트 정보 _.go( {user_id: '101'}, f1, comments_to_user_names, _.count_by, console.log ) 특정인이 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 ) 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, // 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 )




