관리 메뉴

흰둥씨의 개발장

[#4] 재사용 가능한, 모듈적인 코드로 본문

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

[#4] 재사용 가능한, 모듈적인 코드로

돈워리비해삐 2023. 9. 5. 01:16

 

 💡 모듈성?  

프로그램을 작고 독립적인 부분으로 나눌수 있는 정도 

 💡 함수체인과 함수 파이프라인 비교  

함수형 프로그래밍에서 함수는 참조 투명성을 가진 것을 뜻함 (참조 투명성: 동일한 input을 넣으면 동일한 output을 낸다)

- 메서드를 체이닝 (단단한 결합, 제한된 표현성)

- 함수 파이프라인을 배열 (느슨한 결합, 유연성)
   파이프라인 ? 한 함수의 출력이 다음 함수의 입력이 되게 느슨하게 배열한 방향성 함수 순차열 
                        (디자인 패턴 중 '파이프 & 필터' 와 동등한 패턴)
   파이프 함수를 만들기 위한 호환요건 ?
         형식 : 앞단계 함수의 반환형식과 수신함수의 인수 형식이 일치해야 함
         항수 : 수신함수는 앞단계 함수가 반환한 값을 처리하기 위해 적어도 하나 이상의 매개변수 선언해야 함 

f(g(x)) = g(f(x))
//f의 출력 형식과 g의 입력형식이 동일하면 ? 두 함수의 형식은 호환(type-compatible)

 

항수 arity ? 함수가 받는 인수의 개수 (함수형 프로그래밍에서는 항수 개수가 많아질수록 '복잡도'가 증가한다)
=> 단일 책임의 원칙을 지키는 함수를 만들기 위해서 함수의 인수를 가능한 적게 만드는 것이 좋음

함수형 언어가 지원하는 자료구조 '튜플' ? 형식이 다른 원소를 묶어서, 다른 함수에 넘기는 일이 가능한 불변성 자료구조
- 튜플의 장점  : 불변성(한번 만들어진 튜플은 내용변경할 수 없음)  /   임의 형식 생성 방지  /  이형 배열의 생성 방지

const Tuple = function(/*형식*/){
	const typeInfo = Array.prototype.slice.call(args); //튜플에 담긴 인수형식을 읽음
    const _T = function(/*값*/) {              //내부형 _T는 튜플의 형식과 값이 맞는지 확인
    	const values = Array.prototype.slice.call(args);  //튜플에 저장된 값을 꺼냄
        if(values.some( val => val == null || val == undefined)){ //null값 유무 체크
        	throw new ReferenceError('튜플은 null값을 가질 수 없습니다!')'
        }
        if(values.length !== typeInfo.length){ // 정의된 형식 개수와 튜플 항수가 일치하는지 체크
        	throw new TypeError('튜플 항수가 프로토 타입과 맞지 않습니다!')'
        }
        values.forEach((val, idx) => { 
        	this['_' + (idx + 1)] = checkType(typeInfo[idx])(val);// 각 튜플값의 형식이 올바른지 체크
        }, this);
        Object.freeze(this);
      };
      _T.prototype.values = () => {  //튜플 값을 전부 꺼내 배열로 만듦(구조분해 할당이용하면 변수로 매핑가능)
      	return Object.keys(this).map(k => this[k], this);
      };
      return _T;
   };
   
   //튜플 이용하여 isValid 함수 만들기 
   const trim = (str) => str.replace(/^\s*|\s*$/g, '');
   const normalize = (str) => str.replace(/\-/g, '');
   
   const isValid = (str) =>{
   	if(str.length == 0) {
    	return new Status(false, '잘못된 입력입니다');
    } else{
    	return new Status(true, '성공!');
      }
  }
  
  isValid(normalize(trim('444-44-4444')));

 

 💡 커링, 부분적용, 함수바인딩 개념 탐구  

비커리된 함수의 평가 ? 인수를 3개로 설정한 함수에 파라미터를 1개만 넣어 호출하면
나머지 2개 인자 자리에 undefined가 자동할당되어 실행 

const a = 'a';
const b = 'b';
const c = 'c';

function f(a, b, c) {
		console.log(a,b,c)
        }
f(a); //a undefined undefined
//파라미터로 2개를 덜 전달하면 실제로 f(a, undefined, undefined)로 실행됨

 

커리된 평가 ? 인수를 3개로 설정한 함수에 파라미터를 1개만 넣어 호출하면
나머지 2개 인자가 채워지기 기다리는 새로운 함수가 리턴됨

//만약... 위 f(a,b,c)가 커링이 적용되어 있다면

f(a);// f(b,c)가 리턴됨
f(a,b) //f(c)가 리턴됨



커링 currying ? 다변수 함수가 인수를 전부 받을 때 까지 실행을 "지연 Lazy"시켜 단계별로 나뉜 단항함수의 순차열로 전환하는 기법
-> 팩토리 메서드 사용할 때 
-> 함수 템플릿 구현할 때  (함수 템플릿? 생성시점에 커리된 인수개수를 기준으로 연관된 함수를 묶어둔 것)

 

부분 적용 partial application ? 함수의 일부 매개변수 값을 처음부터 고정시켜 항수가 더 작은 함수를 생성함 
-> 커링과 비슷한것같아 보이는데!? 다른점 ? "매개변수를 전달하는 내부 메커니즘이 다름"
     - 커링 : 부분호출시마다 단항함수를 중첩생성하고, 단계별 합성을 통해 최종 결과를 냄(부분적용을 자동화한것)

f: (a) => (b) => ... => (n) => result


     - 부분적용 : 이미 존재하는 함수에 인자 일부만 제공하여 새로운 함수를 생성함 

const add = (a, b) => a + b;
const add5 = (b) => add(5, b); // 부분적으로 적용된 함수
console.log(add5(3));

 

 

 

 💡 함수 합성으로 모듈적인 프로그램 제작  

함수형 프로그램의 목표는 "합성을 유도하는 필요한 자료구조"를 가지는 것 

함수 합성 ? 복잡한 작업을 간단히 쪼개는 것
(서술부와 평가부를 분리하는 아름다움을 느끼자 => 지연평가로 내가 원하는 시점에 평가하기)

무인수 프로그래밍 ? pipe함수안에 함수를 나열할때, 매개변수를 적지 않는 것 

R.compose(first, get, reverse, sort, combine);
//first(a,b,c), get(b)이런식으로 매개변수를 적지 않는 코드

 

암묵적 프로그래밍 ? 커링안의 함수들처럼 인수를 유연하게 정의하는 것 

 

 

 💡 함수 조합기로 프로그램의 흐름을 개선  

명령형 코드에서는 if-else, for같은 장치로 흐름 제어함

함수 조합기 ? 함수형코드에서 if-else, for같은 제어 장치 역할을 하는 고계함수

- identity ? 주어진 인수와 똑같은 값을 반환함

identity :: (a) => a

 

- tap ? 입력 객체 a와 함수 하나를 받아서, a에 받은 함수를 실행하고 다시 a를 반환

tap :: (a -> f) -> a -> a

 

- alt ? 함수 두개를 인수로 받아서 truty한 값이 있으면 첫째 함수의 결과를, 그렇지 않으면 두번째 함수를 반환

const alt = R.curry( (func1, func2, val) => func1(val) || func2(val) );

 

- seq ?  두개 이상의 함수를 인수로 받아서, 동일한 값에 대해 각 함수를 차례로 실행하는 또 다른 함수를 반환 

seq(
append('#id'), //append실행하고
consoleLog     //consoleLog실행됨
)

//실행만 되고 따로 값반환 하지 않음

 

- fork ? 하나의 입력을 두가지 함수(방법)으로 처리하고 그 결과를 다시 조합함 (이때 결과를 조합하는 것이 join)

const fork = (join, func1, func2) => { //함수형 도구 (자바 프레임워크 아님)
	return function(val){
    	return join(func1(val), func2(val));
        }
  }