티스토리 뷰

 

함수형 프로그램밍과 관련해 공부한 내용을 정리합니다.

 

객체지향 패러다임에서 함수형 패러다임으로 넘어온다는 것은 무엇인가

 


 

패러다임?

'내가 짠 코드는 좋은 코드일까?'

 

좋은 코드에 대해서 명확한 기준은 없지만
적어도 구조적으로 좋은 설계를 가지고 있을수록 좋은 코드가 된다는 것을 다들 어렴풋이 알것이라 생각합니다.

 

프로그램이 커질수록 우리는 좋은 설계를 유지하려는 노력이 필요합니다.

좋은 설계는 한번의 작업으로 끝내는 것이 아니라, 코드 전반에 걸쳐 일관적인 원칙과 규칙으로 작성되어야 만들수 있죠.

 

이러한 원칙의 방법이 되는 관점패러다임이라고 부릅니다.

 


함수형 패러다임

객체지향 프로그래밍 패러다임은 객체를 중심으로 사고하고 프로그램을 작성하는 것.
함수형 프로그래밍 패러다임은 데이터를 함수로 연결하는 것을 중심으로 사고하고 프로그래밍을 하는 것을 뜻합니다.

 

 

어디서 나온건데요?

 

'자바스크립트가 쏘아올린 작은 공'

 

자바스크립트(JS)는 함수형 프로그래밍 기반 위에 객체지향 언어의 껍데기를 씌운 언어.
즉, 멀티 패러다임 언어입니다.

 

JS를 통해 개발자들은 객체지향에 함수형 프로그래밍을 적당히 섞으면 훨씬 더 좋다는 것*을 알게 되었고
이는 이후 개발되는 다른 언어에도 영향을 끼치며 멀티패러다임의 근간을 마련해주게 됩니다.

 

*함수를 값으로 넘길 수 있고 익명함수와 클로저를 통해서 값을 보관하고 전달할 수 있다는 개념을 통해서 기존의 객체지향에서 복잡하게 구현을 해야했단 수많은 패턴들이 아주 아주 간단하게 해결이 되는 것을 확인.

 

 

그래서 왜 쓰나요?

함수형 프로그래밍은 객체지향 프로그래밍에서 프로그램을 더 단순하고 간결하게 바라볼 수 있도록 도와줍니다!

함수형 프로그래밍에 충실할수록 재사용성이 높고 테스트를 하기 좋은 코드형태가 됩니다!

 

 

 


 

함수형 프로그래밍 다음과 같은 특징을 가집니다.

 

1. 순수함수 (Pure function)

  • 동일한 입력에 항상 같은 값을 반환해야 하는 함수.
  • 함수의 실행이 프로그램의 실행에 영향을 미치지 않아야 하는 함수.
  • 함수 내부에서 인자의 값을 변경하거나 프로그램 상태를 변경하는 Side Effect가 없는 것.

순수 함수는 프로그램의 변화 없이 입력 값에 대한 결과를 예상 할 수 있어 테스트가 용이합니다.

 

반대로  외부 상태에게 의존적이면,  

외부 상태에 따라 함수의 결과 값도 바뀌게 되어 예측하기 매우 어려워지죠.

 

함수형 프로그래밍에선 순수함수 이용을 지향합니다.

 

 

2. 비상태, 불변성 (Stateless, Immutability)

  • 함수형 프로그래밍에서의 데이터는 변하지 않는 불변성을 유지해야 함.
  • 데이터의 변경이 필요한 경우, 원본 데이터 구조를 변경하지 않고 그 데이터의 복사본을 만들어서 그 일부를 변경하고, 변경한 복사본을 사용해 작업을 진행.

 

 

3. 문을 사용하지 않고, 식을 사용한다.

  • 함수형 프로그래밍은 forifswitch ... 등과 같은 문을 사용하지 않고, 값을 반환하는 식을 사용한다.

문을 사용하는 경우는 명령형, 절차지향형 프로그래밍이다.

 

이는 개발자가 하나 하나 어떤 절차를 밟아가야 하는지 다 서술해야하기 때문에

가독성이 떨어지고, 유지보수하기도 어려워진다.

 

// 문 
let numbers = [1, 2, 3];

function multiply(numbers, multiplier){
  for (let i = 0; i < numbers.length; i++) {
    numbers[i] = numbers[i] * multiplier;
  }
}


// 식
let numbers = [1, 2, 3];

function multiply(numbers, multiplier){
  return number.map(num => num * multiplier);
}

 


식은 파이프라인과 유사하게 어떤 입력을 받으면 결과를 반환합니다.

파이프라인의 내부는 관심이 없고, '해당 파이프라인에 입력을 넣으니 우리가 원하는 출력을 얻을 수 있다!' 는게

함수형 프로그래밍의 주된 목적입니다.

 

 

4. 1급 객체와 고차함수 (Fist-class, Higher-order functions) ⭐

함수형 프로그래밍에서는 함수가 1급 객체가 됩니다.

 

  • 함수를 변수에 할당하거나, 인수로 함수를 전달할 수 있다. 즉 함수를 처럼 다룰 수 있다 !
// 1급 객체
const addTwo = (num) => num + 2;
const multiplyTwo = (num) => num * 2;
const transform = (numbers) => numbers.map(addTwo).map(multiplyTwo);

console.log(transform([1, 2, 3, 4])); // [6, 8, 10, 12]

 

값을 사용할 수 있는 곳(변수 할당문, 객체의 프로퍼티 값, 배열의 요소, 함수 호출의 인수, 함수 반환문)이라면 어디서든지 리터럴로 정의할 수 있습니다.

 

 

이렇듯 함수는 일급객체이기 때문에 고차 함수의 속성을 가집니다. 

 

  • 함수를 인자로써 전달 할 수 있어야 한다.
  • 함수의 반환 값으로 또 다른 함수를 사용 할 수 있다.
// 고차 함수
const addInform = (name) => (age) => age + name;
const jongmin = addInform("rimo");

console.log(jongmin("23")); // 23rimo

 

 

 

길었지만 '일급 객체의 특징을 활용하여 프로그래밍을 하는 것이 바로 함수형 프로그래밍이다' 가 핵심이라고 할 수 있습니다.

 

 


 

커링(currying)

'함수를 반환하는 함수'

 

고차함수에 포함되는 개념 중 '함수를 반환하는 함수'를 커링이라고 합니다.

커링은 함수의 재활용성을 높혀주고 파라미터를 나누어 전달할 수 있도록 해줍니다.

 

 

 

`add` 함수를 재활용하여 사용해 보죠.

function add(a,b){
    return a+b;
}

 

다음과 같이 \(a+2\) 해주는 함수 `addTwo`를 만들수 있습니다.

function addTwo(a){
    return add(a, 2);
}

 

 

만약 `addThree, addFour...` 가 필요하다면 ? 아래와 같이 여러 함수를 만들어야 할 겁니다.

function addTwo(a){
    return add(a, 2);
}
function addThree(a){
    return add(a, 3);
}
function addFour(a){
    return add(a, 4);
}

 

 

 

이를 커링을 적용하면 아래와 같이 나타낼 수 있습니다.

function addX(x){
  return function(a){
    return add(a, x);
  }
}

const addTwo = addX(2);
const addThree = addX(3);
const addFour = addX(4);

addTwo(2)   // 2+2 = 4
addThree(5) // 3+5 = 8
addFour(1)  // 4+1 = 5

 

  • `addX`는 인자 x를 받아서 익명함수를 반환한다. 그 함수는 `function(a){return add(a,x);}` 형태.
  • 반환받은 값인 함수는 인자 a를 요구한다. a를 넣어주면 add(a,x)의 실행 결과를 반환한다.

 

 

화살표 함수로 나타내면 더욱 간단해집니다.

const addX = x => a => add(a,x);

 


 

JS에선 함수가 일급객체로 취급되고 그 특징에 따라 함수의 인자로 또 다른 함수도 받을 수 있습니다.

 

그럼 방정식 \( 4x(x+2)\)을 수행할 함수를 만들어볼까요.

const add = (a,b) => a+b;
const multiply = (a,b) => a*b;

const addX = x => a => add(a, x);
const addTwo = addX(2);

const multiplyX = x => a => multiply(a, x);
const multiplyFour = multiplyX(4);

 

이렇게 커링을 이용해 `addTwo, multiplyFour` 함수를 만들었습니다.

 

이제 둘을 곱해주는 커링을 만들어 주면됩니다.

const compose = fn => fn2 => x => fn2(x) * fn(x);
const equation = compose(addTwo)(multiplyFour);

 

 

이제 x값에 따라 다양한 결과를 받을 수 있게 되었습니다.

  equation(10) // (4 * 10) * (10 + 2) = 40 * 12 = 480

 

 

 

 만약 추가적으로 5x(x+2)이 필요해 졌다면?

const add = (a,b) => a+b;
const multiply = (a,b) => a*b;

const addX = x => a => add(a, x);
const addTwo = addX(2);

const multiplyX = x => a => multiply(a, x);
const multiplyFour = multiplyX(4);
const multiplyFive = multiplyX(5); // <-- 추가!!

const compose = fn => fn2 => x => fn2(x) * fn(x);
const composeAddTwo = compose(addTwo);

const equation1 = composeAddTwo(multiplyFour); // 4x(x+2)
const equation2 = composeAddTwo(multiplyFive); // 5x(x+2)

 

위와 같이 인자로 받은 함수들을 교체해주면서, 유사한 함수들을 여러개 만들 수 있습니다.

 

 


 

[참고자료]

 

 

[함수형] 커링 Currying을 배워보자.

함수형 시리즈 [함수형] 클로저 Closure에 대해 알아보자. 클로저 클로저란, 내부 함수가 함수가 참조하고 있는 주변 환경에 접근할 수 있는 것을 의미한다. MDN에서는, 클로저는 독립적인 (자유) 변

gobae.tistory.com

 

 

함수형 프로그래밍이란?

jongminfire.dev

 

 

JavaScript | 함수형 프로그래밍

클린한 코드를 작성하기 위해 공부를 시작하면 항상 함수형 프로그래밍이란 단어를 듣게된다. 함수형 프로그래밍은 무엇이며, 어떤 이유 때문에 우리에게 익숙한 객체지향 프로그래밍이 아닌

velog.io

 

 

다시 쓰는 함수형 프로그래밍

> 참 좋은데 어떻게 표현할 방법이 없네... 오랜 기간 개발을 공부하게 되면서 여러가지 패러다임의 변화를 겪었는데 그 중에서 인상깊었던 것중에 하나는 객체지향 패러다임에서 함수형 패러다

velog.io

 

 


 

공부한 내용을 복습/기록하기 위해 작성한 글이므로 내용에 오류가 있을 수 있습니다.

댓글
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Total
Today
Yesterday