ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 함수형 프로그래밍 - 평가와 일급, 고차함수, 리스트 순회
    CS지식/함수형 프로그래밍 2024. 3. 25. 12:42

    평가와 일급

    평가

     

    코드가 계산(Evaluation)되어 값을 만드는 것이다. 즉 평가라는 것은 코드가 계산되는 것을 의미한다.

     

    일급

    const log = console.log;
    //10은 값으로 다룰 수 있다.
    const a = 10;    
    const add10 = a => a +10; 
    //add10의 인자로 변수a가 전달 될 수 있다.
    add10(a);    
    //a +10은 함수의 결과로 사용된다.
    //즉 다시 변수 r에 담아 사용가능하다.
    const r = add10(a); 
    log(r);
    • 으로 다룰 수 있다.
    • 변수에 담을 수 있다.
    • 함수의 인자로 사용될 수 있다.
    • 함수의 결과(return)로 사용될 수 있다.

     

    일급 함수

    const log = console.log;
    /**일급함수 */
    const add5 = a => a + 5;
    //함수의 인자로 함수가 사용될 수 있다.
    log(add5);
    // 함수는 평가 후 결과를 다시 함수에 전달할 수 있다. 
    log(add5(5));
    
    
    //함수 f1은 () => 1(1을 return)하는 함수를 
    //결과 값으로 return할 수 있다.
    const f1 = () => () => 1;
    log(f1()); //[Function (anonymous)]
    
    //f1의 담긴 () => 1는
    //다시 다른 변수에 담길 수 있다.
    const f2 = f1();
    log(f2); //[Function (anonymous)]
    
    //그렇게 다른 변수에 담은 함수를 
    //원하는 시점에 평가할 수 있다.
    log(f2()); //1

     

    javascript에서 함수는 일급이다.  함수가 일급이라는 이야기는 1)함수를 값으로 다룰수 있다는 뜻이다. 그리고 함수는 평가 후 결과를 만들어서 값을 또 2)다른 함수에게 전달 할 수 있다. 함수가 일급이라는 것은 3)함수의 결과 값으로 즉 return 값으로도 사용될 수 있다.

    • 함수를 값으로 다룰 수 있다.
    • 조합성추상화의 도구

     

    함수형 프로그래밍에서는 함수가 일급이라는 성질을 이용해서 많은 조합성을 만들어 내고, 추상화의 좋은 도구로 사용하고 있다. 


    고차 함수

    고차함수는 이러한 사실을 이용해서(함수가 일급이라는 사실) 함수를 값으로 다루는 함수다. 

    함수를 인자로 받어서 실행하는 함수(applicative)

     

    apply

    const log = console.log;
    
    const apply1 = f => f(1);
    const add2 = a => a + 2;
    log(apply1(add2)); //3
    log(apply1(a => a - 1)); //1

     

    • apply1 함수는 매개변수로 함수를 받아서 매개변수로 받은 함수에 숫자 1을 대입해서 실행하는 함수이다. 즉 함수를 매개변수로 다루고 있다.
    • apply1 메서드에 add2 함수를 매개변수로 넣어서 실행하면 평가의 값을 3으로 판단하고 있는 것을 확인 할 수 있다.

     

    time

    const log = console.log;
    const times = (f , n) => {
        let i = -1;
        while(++i < n) f(i);
    }
    times(log, 3); //0, //1, //2
    
    times(a => log(a + 10), 3); //10, //11, //12

     

    • times함수는  함수를  첫번째 매개변수로 받고, 두 번 째 매개변수 n 만큼 함수 f를 실행한다.  

     

    함수를 만들어 리턴하는 함수 (클로저를 만들어 리턴하는 함수)

     

    addMaker

    const addMaker = a => b => a + b;

     

    • a를 먼저 매개변수로 받고 그것에 대한 결과로 b  => a + b를 return 한다. 즉 a를 매개변수로 받는 함수는 결과로 매개변수 b를 가지는 함수를 return하는 것이다.  

     

     

     

    add10

    const log = console.log;
    
    const addMaker = a => b => a + b;
    const add10 = addMaker(10);
    
    log(add10(5)); //15
    log(add10(10)); //20

    • addMaker에 10을 대입해서 add10함수를 만든다. 이렇게 만들어진 add10함수에 매개변수 5를 넣으면 add10(5)의 결과로 15가 반환된다. 
    • addMaker로 반환되는 함수는 addMaker의 결과 값이자 클로저(closure)이다.
    • addMaker 같은 함수는 결국 특정 클로저가진 함수를 만들어서 return 하기 위해 사용 한다.

    리스트 순회

    const list = [1, 2, 3];
    const str = 'abc';
    
    for(item of list){
        log(item);
    }
    
    for(item of str){
        log(item);
    }

     

    • es6부터 루프 문이 어떤 방식으로 구체적으로 배열을 순회하기 보다는 선언적인 방식으로 간결하게 변경되었다. 
    • es6가 단순하게 for문을 간결하게 사용하게 만들어 준 것 이상으로 어떻게 순회하는 코드에 대해 추상화를 했고 사용성을 개선했는지에 대해 알아 보자.

     

    Array, Set, Map 을 통해 알아보는 이터러블/이터레이터 프로토콜

    javascript는 내장 객체로 Array, Set, Map를 가지고 있고 이 객체들을 모두 for 구문으로 순회할 수 있다. 

    // Array를 통해 알아보기
    log('Arr -------------');
    const arr = [1, 2, 3];
    for (const a of arr) log(a); //1, //2, //3
    log(arr[0]); //1
    log(arr[1]); //2
    
    
    //Set을 통해 알아보기
    log('Set -------------');
    const set = new Set([1, 2, 3]);
    for (const s of set) log(s); //1, //2, //3
    log(set); //Set(3) {1,2,3}
    log(set[0]); //undefined
    
    //Map을 통해 알아보기
    log('Map -------------');
    const map = new Map([['a', 1],['b', 2],['c',3]])
    for(m of map) log(m); //['a',1], //['b',2], //['c',3]

     

     

    • Array는 각 배열의 index로 배열의 값에 접근 할 수 있는 반면 Set은 그런 접근이 불가능하다. 
    • Set객체에 index를 사용해서 접근할 수 없다는 것은  Set을 순회하는 for...of 문의 내부 구현이 과거의 for문으로 Array 순회를 구현한 것과 다른다는 것을 의미한다.
    • Map역시 index로 각 요소에 접근이 불가능 한것을 Set과 동일하게 for...of문의 내부구현이 과거의 Array를 순회했던 for문과 다를 것을 의미한다.

     


     

    Symbol.iterator

    es6 부터 Symbol형이 추가되었다. Symbol은 특정 객체의 key로 사용될 수 있다.

     

    Symbol.iterator 찍어보기

    const log = console.log;
    
    const arr = [1,2,3];
    log(arr[Symbol.iterator]); //[Function: values]

     

    values라는 함수가 들어있다는 것을 알 수 있다.

     

    Symbol.iterator에 

    log('Arr -------------');
    const arr = [1, 2, 3];
    arr[Symbol.iterator] = null;
    for (const a of arr) log(a);

     

    arr[Symbol.iterator]에 null 값을 대입하면 위와 같이 해당 배열이 iterable하지 않다는 에러가 발생하며 for문이 실행되지 않는 것을 확인할 수 있다. 따라서  arr[Symbol.iterator]에 담겨있는 values라는 함수가 for..of문과 연과이 있음을 추측해 볼수 있다. Array와 마찬가지로 Set객체와 Map 객체도 Symbol.iterator에 values라는 함수를 가지고 있고 이것이 for..of문을 구현해 주고있다.


     

    이터러블(iterable)/이터레이터 프로토콜

    Array,Set,Map은 JavaScript에 있는 내장 객체로써 이터러블/이터레이터 프로토콜을 따르고 있다. 

    log('Arr -------------');
    const arr = [1, 2, 3]; // arr는 '이터러블'이다.
    for (const a of arr) log(a); //1, //2, //3
    
    let iterator = arr[Symbol.iterator]();
    log(iterator.next()); // {value:1, done: false}
    • 이터러블 : 이터레이터를 리턴하는 [Symbol.iterator]()메서드를 가진값 
      • ex) arr는 이터러블(iterable)이다.
      • arr가 이터러블인 이유는 이터레이터를 return하는 [Symbol.iterator]메서드를 가지고 있기 때문이다.
      • [Symbol.iterator]() 를 실행하면 Array iterator를 반환한다.
    • 이터레이터 : next()메서드를 통해서  { value, done } 객체를 리턴한다.
      • arr[Symbol.iterator]()를 iterator이라는 변수에 담고 첫번째로  iterator.next()를 출력하면 {value:1, done:false}를 반환한다.
    • 이터러블/이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록한 규약 

    for..of 문 같은 경우는 대입될 Array객체가 이터러블이고 또 Array는 [Symbol.iterator]()를 통해서 이터레이터를 return하기 한다. 다시한번 정리하면 Array는 for..of문과 동작하게 될 이터러블 객체이고, for...of를 통해서 순회할 수 있기 때문에 이터러블/이터레이터프로토콜을 따른다고 할 수 있다. 

     

    위 코드에서 for..of문은 작동할 때마다 arr[Symbol.iterator]().next()를 실행해서 value 속성에 들어오는 값을 a담고, done이 false가 되면 done에 true값이 담기면 for문에서 빠져나오도록 되어 있다. 


    iterator.next() 2번 호출

    const log = console.log;
    
    log('Arr -------------');
    const arr = [1, 2, 3];
    let iterator = arr[Symbol.iterator]();
    
    iterator.next();
    iterator.next();
    log(iterator.next()); //{ value: 3, done: false }

     

    iterator.next() 3번 호출

    const log = console.log;
    
    log('Arr -------------');
    const arr = [1, 2, 3];
    let iterator = arr[Symbol.iterator]();
    
    iterator.next();
    iterator.next();
    iterator.next();
    log(iterator.next()); //{ value: undefined, done: true }

     

    iterator.next() 요소 2본 호출 후 for문 으로 iterator 호출

    const log = console.log;
    
    log('Arr -------------');
    const arr = [1, 2, 3];
    
    let iterator = arr[Symbol.iterator]();
    log(iterator.next()); // {value:1, done: false}
    
    iterator.next();
    iterator.next();
    for(const a of iterator) log(a); //3

     

    iterator.next()를 이미 2번 호출한 후에도  iterator를 사용해서 for문을 호출할 수 있다. 이때 for .. of문의 로직을 통해서 출력되는거 3 한번 뿐이다.  

     

    다시말해 for문은 iterator를 Symbol.iterator(배열 arr)의 next()함수를 배열의 요소 개수 만큼 실행 시켜주는데 next()메서드가 반환한 value를 a변수를 넣고 a를 활용한 for..of문의 로직이 실행된다. 하지만 iterator를 통해서 이미 next()가 2번 실행 됐었고 이때는 iterator의 커서가 arr의 2번째 요소를 가르키고 있다. 그래서  for...of문이 실행되면서 마지막으로 next()메서드는 {value : 3, done: false }를 반환하고 종료된다. 물론 value값이 a의 대입되는 방식으로 for...of문이 실행된다. 


     

    Set 객체의 for...of문 동작

    //Set을 통해 알아보기
    log('Set -------------');
    const set = new Set([1, 2, 3]);
    for (const s of set) log(s); //1, //2, //3
    
    log(set); //Set(3) {1,2,3}
    log(set[0]); // undefined

     

    따라서 Set 객체가  set[0]과 같은 배열의 방식으로 요소에 접근할 수 없더라고 for..of문을 통해 동작하는 것은 과거에 사용했던 for문과 같은 형태에서 증가하는 i값을 사용해 인덱스에 접근하는 방식으로 for..of문을 실행 시키는 것이 아니라  이터러블/이터레이터 프로토콜 를 따르고 이를 통해서 동작하기 때문이다.

     

     

    set[Symbol.iterator]()의 next()메서드 호출 

    //Set을 통해 알아보기
    log('Set -------------');
    const set = new Set([1, 2, 3]);
    let iterSet = set[Symbol.iterator]();
    
    iterSet.next();
    iterSet.next();
    log(iterSet.next()); //{ value: 3, done: false }

     

    Set 객체 역시 이터러블/이터레이터 프로토콜를 사용해서 set의 요소를 순회하고 있다는 것을 확인할 수 있다.

     


     

    map[Symbol.iterator]()의 next()메서드 호출 

    log('Map -------------');
    const map = new Map([['a', 1],['b', 2],['c',3]])
    let iterMap = map[Symbol.iterator]();
    
    iterMap.next();
    iterMap.next();
    log(iterMap.next()); //{ value: [ 'c', 3 ], done: false }

     

    Map 객체 역시 이터러블/이터레이터 프로토콜를 사용해서 map의 요소를 순회하고 있다는 것을 확인할 수 있다.

     

     

    map.keys() 이터레이터

    log('Map -------------');
    const map = new Map([['a', 1],['b', 2],['c',3]])
    log(map.keys()); //[Map Iterator] { 'a', 'b', 'c' }
    
    const mapKeys = map.keys();
    log(mapKeys.next());  //{ value: 'a', done: false }
    log(mapKeys.next());  //{ value: 'b', done: false }
    log(mapKeys.next());  //{ value: 'c', done: false }

     

    Map 객체 경우에 keys()라는 함수가 존재한다. keys()라는 메서드는  [Map Iterator] {'a','b','c'}를 반환한다. 해당 이터레이터를 mapKeys에 담고 next()함수를 호출했을 때   반환하는 객체의 value의 속성의 값의 key값 밖에 없는 것을 확인할 수 있다.

     

     

    Map객체의 keys, values, entries

    log('Map -------------');
    const map = new Map([['a', 1],['b', 2],['c',3]])
    
    // keys,vlaue,entries
    for (const a of map.keys()) log(a);    //a, //b, //c
    for (const a of map.values()) log(a);  //1, //2, //3
    for (const a of map.entries()) log(a); //['a',1], //['b',2], //['c',3]

     

     

    이터레이터가 가진 Symbol.iterator

    log('Map -------------');
    const map = new Map([['a', 1],['b', 2],['c',3]])
    const iterVal = map.values();
    
    log(iterVal); //[Map Iterator] { 1, 2, 3 }
    log(iterVal[Symbol.iterator]); //[Function: [Symbol.iterator]]
    
    const iterVal2 = iterVal[Symbol.iterator]();
    log(iterVal2.next()); //{ value: 1, done: false }

     

    map.values() 함수를 사용해 반환받은 만든iterVal 이터레이터에는 다시 Symbol.iterator가 들어있다. [Symbol.iterator]() 함수를 그대로 가지고 있기 때문에 for...of문을 value()함수를 통해 만든 이터레이터로 다시 순회할 수 있는 것이다. 

     

    iterVal[Symbol.iterator]() 함수는 자기자신을 그대로 반환하도록 되어 있기 때문에 이것의 반환값을 iterVal2에 담아서 next()함수를 실행시킬 수 있다.

     

     

     

    [출처 - 함수형 프로그래밍과 JavaScript ES6+, 유인동]

    https://www.inflearn.com/course/functional-es6

     

    함수형 프로그래밍과 JavaScript ES6+ 강의 - 인프런

    ES6+와 함수형 프로그래밍을 배울 수 있는 강의입니다. 이 강좌에서는 ES6+의 이터러블/이터레이터/제너레이터 프로토콜을 상세히 다루고 응용합니다. 이터러블을 기반으로한 함수형 프로그래밍,

    www.inflearn.com

     

    댓글

Designed by Tistory.