관리 메뉴

흰둥씨의 개발장

[#3] 자료구조는 적게, 일은 많이 본문

함수형 프로그래밍/함수형 자바스크립트

[#3] 자료구조는 적게, 일은 많이

돈워리비해삐 2023. 9. 1. 00:17

✅ 프로그램 제어와 흐름

📎 제어흐름 (control flow) ?  프로그래밍 정답에 이르기까지 거치는 경로 
ㄴ 명령형의 경우 - 분기, 루프에 따라 움직이는 일련의 연산(구문)으로 구성됨
ㄴ 선언형의 경우 - 서로 연결된 블랙박스 연산을 제어함 (= 고수준으로 추상화된 함수을 가지고 체이닝함)

함수형 으로 작성한 코드에서 고수준 추상화 함수로 가는 이유 ?
ㄴ 콜백처럼 로직을 파악하려면 가장 안쪽에 감싼 내부 함수부터 하나씩 읽어야 하기 때문에 "가독성이 떨어짐"
ㄴ 이미 존재하는 자료구조(배열, 이터러블...등)를 이용해 고계연산을 적용하여, 에러가능성은 줄이고, 유지보수시 편하게 함
ㄴ 부수 효과를 일으킬만한 기존의 수동 루프를 대체함 

📎 람다 표현식 ? 한줄 짜리 익명함수를 일반 함수 선언보다 단축구문으로 나타냄

const 더하기 = (a, b) => a + b; 
console.log(더하기(1,2)); //3

//위와 같이 '=>' 이후 우변에는 단일 표현식이나 / 여러 구문이 포함된 블록문이 옴

ㄴ 위 예제에서 '더하기()'는 실제로 존재하는 값이 아니라, 그 값을 얻기위해 느긋한 방법(Lazy)을 취하고 있음 
ㄴ 람다 표현식은 map , filter, reduce와 잘 어울려서 같이 사용 적극 권장 

*상식 한줄 ) 자바스크립트의 전신인 LISP(원조 함수형언어)은 list processor의 줄인말로 '자료 리스트를 처리하는 코드'로 역할수행함

📎 로대시JS의 함수형 도구 알아보기 

1)  _.map: 데이터를 변환 

let result = [];
let persons = [{fullname:'신짱구'} ,{fullname:'신짱아'},{fullname:'신형만'},{fullname:'봉미선'}];

_.map(persons, s => (s !== null && s !== undefined) ? s.fullname : '');
//['신짱구', '신짱아', '신형만', '봉미선'];
//map의 구현부

const _ = {};
_.map = (arr, fn) => {
	const len = arr.length;
    let result = new Array(len);
    for(let i = 0; i < len; ++i){
    	result[i] = fn(arr[i], i, arr); //함수 fn을 각원소에 실행한후 그 결과를 빈배열에 쌓기
    }
    return result;
}

 

2) _.reduce : 결과를 수집

const _ = {};

_.reduce = (arr, fn, accumulator) => {
	let i = -1;
    let len = arr.length;
    
    if(!accumulator && len > 0) { //누적치를 지정하지 않으면 
    	accumulator = arr[++i];   //배열의 첫번째 원소를 초깃값으로 설정한다.
      }
      
    while(++i < len){ //배열을 반복하면서 
    accumulator = fn(accumulator, arr[i], i, arr); //원소마다 누적치, 현재값, 인덱스, 배열을 fn에 인수로 넘김 
    }
    return accumulator;//누적치를 반환
}

//fn: 배열 각 원소마다 실행할 이터레이터 함수
//accumulator : 계산할 초깃값으로 넘겨받는 인수(계속 함수호출을 거치면서 계산된 결과값을 저장함)

ㄴreduce는 accumulator에 의존하기 때문에 '결합법칙'이 성립하지 않는 연산은 진행순서에 다라 결과가 달라짐 (왼 -> 오, 왼<-오)

3) _.filter : 원하지 않는 원소제거 

const _ = {};

_.filter = (arr, predicate) => {
	let i = -1;
    let len = arr.length;
    result =[];
    
    while(++i < len){
    	let value = arr[i];
        if(predicate(value, i, this)){ //predicate의 실행결과가 true이면 result에 담기, false이면 버림
        	result.push(value);
            }
       }
    return result;
}

 

 

✅ 코드와 데이터를 효과적으로 헤아림

📎  불변성과 순수함수를 사용하면 코드를 효과적으로 헤아릴수 있게 됨

 

✅ 재귀적 사고방식

📎 재귀 recursion ? 주어진 문제를 자기 반복적인 문제들로 잘게 분해한 다음, 이들을 다시 조합해 정답을 찾는 기법
ㄴ 표현적인 방식으로 반복을 대체하는 것 

📎 재귀함수의 구성요소 ? 
- 기저 케이스 base case = 종료 조건 
- 재귀 케이스 

function sum (arr){
	if(_.isEmpty(arr)){ //종료 조건
    	return 0; 
    }
    return _first(arr) + sum(_.rest(arr)); //재귀케이스 : 입력을 점점 줄여가면서 자신을 호출함 
}

sum([1,2,3,4,5,6,7,8,9,10]) //55;

 

📎 재귀 알고리즘 ? 루트부터 모든 자식노드를 타고 내려가면서 전체 트리를 '전위순회'함