관리 메뉴

흰둥씨의 개발장

[#2] 고계 자바스크립트 본문

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

[#2] 고계 자바스크립트

돈워리비해삐 2023. 8. 30. 00:40

➡️ 자바스크립트가 함수형 언어로 적합한 이유 

1. 어디에나 있고 가장 널리 쓰임
2. 동적 형식
    (dynamically typed => 다형성있는 함수를 만들 수 있음)
3. ES6에서 함수형 프로그래밍에 걸맞는 기능들 많이 추가됨
    (화살표함수, 상수, 이터레이터, promise...)
4. 객체지향과 함수형 두 패러다임을 함께 사용할수있는 하이브리드 언어
    (=> 자바스크립트는 다중 패러다임 개발이 가능한 언어)

✔️ 객체지향과 함수형의 가장 큰 차이는 "데이터(객체 속성)와 기능(함수)를 조직하는 방법"에 있음 
ㄴ> 객체지향 : 인스턴스 메서드를 통해 조작
ㄴ> 함수형 : 두루두루 적용 가능하고, 굵게 나뉜(coarse-grained) 연산에 의존

 

➡️ 불변성 및 변경에 대한 정책

자바스크립트의 동적 타이핑은
프로그램의 "상태 state"를 다루는 측면에서는 (프로젝트 규모가 커질수록) 관리어려운 코드가 될 가능성 큼

✔️ 불변성을 지키기 위한 몇가지 제안
  1. 객체를 값으로 취급 
  ㄴ 자바스크립트에서는 객체타입데이터는 상수에 할당되어도 변경가능하기 때문에, 
     - 캡슐화 하거나
     - **값객체 패턴을 사용해보기 (객체 구조 단순하다면)

**값 객체 패턴 : 객체의 equality가 identity나 레퍼런스가 아닌 오직 값에 따라 좌우되는 객체
ㄴ 일단 값객체를 선언한 이후에 그 상태는 변하지 않음

//값 객체 예시 
function coordinate(lat, long){
	let _lat = lat;
    let _long = long;
    
    return {
    latitude: ()=> {
    	return _lat;
        },
    longitude: ()=> {
    	return _long;
        },
    translate: (dx, dy) => {
    	return coordinate(_lat + dx, _long + dy);
        },
    toString: ()=> {
    	return `(${_lat}, ${_long})`;
        }
   };
}

const seoulJunggu = coordinate(37.56100278, 126.9996417);
seoulJunggu.toString();

 

  2. 가동부를 깊이 동결 
  ㄴ Object.freeze()함수의 writable속성을 false로 setting해서 객세 상태를 못바꾸게 동결하기 
  ㄴ 다만 해당 방법으로 중첩된 객체 속성까지 동결하는 것은 불가능함 (얕은 연산만 처리됨)
  ㄴ 중첩데이터까지 동결하고 싶을땐 재귀함수로 깊은 depths까지 접근하여 Object.freeze()함수 적용하기

const person = Object.freeze(new Person('하스켈', '커리', '444-44-4444')); //동결하기 

//동결된 객체 person에 아래와 같이 접근하면 허용되지 않음
person.firstName = '두비두밥'; // TypeError: 읽기전용 속성firstName에 값할당 할수 없음

 

  3. 카피 온 라이트전략 => 함수형 레퍼런스 활용하기 (람다js의 함수...등등)

  ✔️ 카피온 라이트?
  컴퓨터 공학에서 기억공간을 관리하는 전략 중 하나,
  데이터 복사 명령을 받아도 원본 주소값만을 기록하여
  마치 원본이 사본인것처럼 보여주다가
  원본 혹은 사본중 어느 한쪽이 수정되면 그제서야 복사를 수행함 (복사를 해야할 시점에 복사를 함)

 

➡️ 고계함수와 일급함수

✔️ 함수 ? 연산자를 적용하여 평가할 수있는 모든 호출 가능 표현식 
ㄴ 사용가능한 결과를 낼 경우에만 유의미하고 (=> 표현식)
ㄴ 이외는 부수효과를 일으킨다고 볼 수 있음   (=> 구문)

ㄴ 자바스크립트에서의 함수는 '객체'이기 때문에 일급first-class이다. 
ㄴ Function형식의 인스턴스

✔️ 고계함수(higher-order-function) ? 함수를 인수로 전달하거나, 함수를 반환하는 것
ㄴ 고계함수를 쓴다는 것은 "선언적 패턴"이 늘어난다는 것 

참고) 자바스크립트의 함수 호출 유형

//전역 함수로 호출 : this는 전역객체, 또는 undefined를 가리킴
function 전역입니다(){
	this.myValue = '임의의 값'; //이 때 this는 전역객체를 가리킴
    }

//메서드로 호출 : this는 해당 메서드를 가지고있는 객체를 가리킴
let 객체 = {
	prop: '하이염',
	getElement : function () {
    	return this.prop; // 이 때 this는 객체를 가리킴
        }
    }
    
//생성자 호출 : 새로만든 객체의 레퍼런스를 암시적으로 반환
function 생성자로호출될예정 (arg) {
	this.prop = arg;
    }
let 이제뉴로만들자 = new 생성자로호출될예정('무슨인수를 넣어야 잘넣었다고 소문이날까');

 

✔️ 함수 메서드 

ㄴ Function.prototype.apply(thisArg, [매개변수 배열])
 Function.prototype.call(thisArg, arg1, arg2, ...)

 

➡️ 클로저와 스코프 개념

✔️ 클로저 closure ? 함수를 선언할 당시의 환경에 함수를 묶어둔 자료구조
ㄴ 함수 선언부의 물리적 위치에 의존함 (= static scope = lexical scope)
ㄴ 클로저는 함수의 스코프를 상속한 것

//클로저를 이해하자
let 전역변수 = 'global';

function 함수(params){
	let 지역변수 = 'local';
    
    function inner(){
    	console.log(`${전역변수}, ${지역변수}, ${params}`);
        }
    return inner;
 }

let inner = 함수('파람스'); 
inner();//global, local, 파람스

//함수()안의 inner()는 전역변수, 지역변수, 자신을 싸고있는 함수의 parameter에 접근할 수 있다.

✔️ 위와 같은 작동원리때문에 함수형프로그래밍에서 '불변성'을 지키기 위해서는 전역스코프를 쓰지 않는 것이 좋음 

✔️ 함수스코프 : 함수내에서 선언된 변수는 함수 밖에서 접근할수 없음 

let X = '임의의 값';

function 부모(){
	function 자식(){
    	console.log(X);
    }
    return 자식;
}

부모();//호출하면 
//1. 'X를 요청한 자식'함수 스코프에서 X있나 찾아봄 -> 없음 
//2. '부모' 함수 스코프에서 X있나 찾아봄 -> 없음
//3. 전역스코프에서 찾아봄 -> 찾음
//4. 만약에 전역스코프에도 없으면 undefined 반환함

 

✔️ 블록 스코프 
ES5 는 블록스코프 지원하지 않음 (단, catch의 error는 예외...)
var키워드 변수와 선언함수는 호이스팅 되어서 전역으로 선언된 효과가 생김 = 블록에 바인딩 되지 않아 흐름이 모호해짐

//ES5 이전
for(var i = 0; i < 10; i++){
	setTimeout(function(){
    	console.log('숫자 : ' + i);
        }, 1000);
}

//ES6 이후 
for(let i = 0; i < 10; i++){
	setTimeout(function(){
    	console.log('숫자 : ' + i);
        }, 1000);
}

ㄴ let과 const 키워드는 블록스코프를 따르므로 호이스팅으로 인한 문제 감소시킬수 있음 

➡️ 클로저의 활용 

1. 프라이빗 변수를 모방
자바스크립트는 private 접근자가 없음 -> 클로저를 이용해서 비스구리 하게 표현해보기 (캡슐화, 즉시실행함수활용)

const zip(code, location){
	let _code = code;    //해당 지역변수를 노출하지 않고 (=>private흉내내기)
    let _loc = location;
    
    return {
    	code: ()=>{
        	return _code;  //값을 리턴함
            },
        location: ()=>{
        	return _loc;
            }
        };
}
let module = (function module(export){
	let private = '';
    
    export.method1 = () =>{
    //작업 수행 
    };
    export.method2 = () =>{
    //작업 수행 
    };
    return export;
}(module || {}));

 

2. 서버 측 비동기 호출 시 중첩대신 선언적인 표현식을 체이닝하여 가독성 좋은 코드 만들기

3. 가상의 블록 스코프 변수를 생성 -> var로 변수 i를 선언해서 생기는 호이스팅 문제를 let으로 바꾸는 것으로 해결할수 있지만, 
forEach와 같은 함수형도구를 사용하면 루프문에 블록스코프가 존재하는 것과 같은 효과를 낼수있어 유용