본문 바로가기

Computer Science/etc.

< FP > 함수형 프로그래밍이란?

1. 함수형 프로그래밍(FP)

* 소개

 - 최근 프로그래밍 패러다임은 크게 명령형 프로그래밍과 선언형 프로그래밍으로 나뉜다.

 

 - 명령형 프로그래밍 : 어떻게 할 것인지를 설명하는 방식으로 절차지향 프로그래밍과 객체지향 프로그래밍이 있다.

 

 - 선언형 프로그래밍 : 무엇을 할 것인지를 설명하는 방식으로 함수를 조합하여 SW를 만드는 함수형 프로그래밍이 있다.

 

 - 함수형 프로그래밍은 자료처리를 수학적 함수의 계산으로 표현하려 한다. f(x) 와 같은 것을 생각하면 좋겠다.

 

* 의미

 - 함수형 프로그래밍은 거의 모든 것을 순수함수로 나누어 문제를 해결하려한다. 이를 통해 가독성을 높이고, 유지보수를 용이하게 한다.

 

 - 함수형 프로그래밍의 가장 큰 특징은 순수함수, 1급함수, 불변성, 참조 투명성이다. 이들은 아래에서 자세히 알아보자.

 

 - 함수형 프로그래밍은 상태값 없이 복사만을 사용하여 구현된다. 함수가 1급시민이기 때문에 함수의 매개변수로 넘길 수 있기 때문이다.

 

 - 상태와 가변데이터 대신 오로지 불변데이터만을 사용한다.

 

 - 순수한 함수형 언어에서는 반복문과 조건문도 사용하지 않고 재귀로만 구현된다. 아래의 예시들은 자바스크립트로 작성하였으며, 순수한 함수형 언어가 아니므로 함수형 프로그래밍을 지향하여 작성했으나, 일부 완전하지 못하므로 참고바란다.

 

2. 특징

* 순수함수

 - 동일한 input 에는 동일한 output이 나와야하는 것을 의미한다.

 

 - 부작용(side-effect)가 없는 함수를 의미한다. 이를 위해 외부의 상태를 변경하거나 외부의 값을 참조하지 않는다.

 

 - 느긋한 계산(Laze-Evaluation)을 사용한다. 이는 아래에서 나오는 계속 복사되는 특성에서 중요한 개념이다. 함수형 프로그래밍은 복사가 빈번하게 이루어지는데, 이것이 직접적으로 계속 일어나는 것이아니라 실제 함수 호출 시 그 값이 필요할 때까지는 계산이 일어나지 않는다. 즉 필요할 때 복사하고 계산한다.

 

* 1급 함수

 - 1급 함수를 알아보기 전에 1급 시민을 알아보자. 1급 시민은 프로그래밍 언어에서 다음 세 가지 조건을 만족한다.

 

  ∙ 대상을 함수의 매개변수로 넘겨 사용할 수 있는가.

  ∙ 대상을 함수의 반환값으로 돌려줄 수 있는가.

  ∙ 대상을 변수나 자료구조에 담을 수 있는가.

 

 - 위의 문장에서 '대상'을 '함수'로 바꾼 것이 1급 함수이다. 즉 함수형 프로그래밍에서는 함수자체를 매개변수로 넘길 수 있으며, 계속 반환 값이 복사되며 사용된다.

 

* 불변성

 - 어떤 값의 상태를 변경하지 않는다는 의미이다. 상태의 변경의 외부에 side-effect를 일으킬 수 있으므로 지양해야한다.

 

* 참조투명성

 - 기존의 값은 변경되지 않고 유지됨을 의미한다.

 

 - 즉 바깥 스코프에 영향을 끼치지 않는다.

 

3. 람다함수와 클로저

* 람다함수

 - 현재 사용되는 람다의 근간은 수학 및 기초CS분야에서의 람다 대수이다.

 

 - 람다 함수는 프로그래밍 언어에서 익명함수를 지칭한다.

 

 - 자바스크립트에서는 모든 함수가 1급객체이므로 익명함수이기만 하면 람다라고도 볼 수 있다.

 

 - 람다식을 통해 고차함수를 사용할 수 있고, 코드를 간결하게 유지할 수 있다.

 

* 클로저

 - 클로저는 실행 당시의 상황을 기억한다. 코드를 통해 자세히 알아보자.

 

function test(n) {
  var number = n;
  function printNumber() {
    console.log(n);
  }
  return printNumber;
}
var print = test(50);
print(); // 50

 

 - 위에서 print = test(50)으로 받은 것이 클로저이다.

 

 - 클로저는 실행당시 test내의 지역변수인 number을 test호출 후에도 기억한다.

 

 - 클로저는 자신을 포함하고 있는 외부의 인자나 지역변수 등을 외부 함수가 종료된 후에도 사용할 수가 있는데, 이를 자유 변수라 한다.

 

 - 클로저가 생성될 때 범위 내의 변수들을 자유변수로 만드는 것을 캡처(capture)라고 한다. 이를 통해 클로저는 마치 상수처럼 계속 같은 값을 보여줄 수 있다.

 

 - 이 자유변수는 직접 접근할 수 없고, 클로저를 통해서만 사용가능하다는 특징이 있다.

 

4. 함수의 합성

 - 함수형 프로그래밍에서는 함수의 조합으로 원하는 값을 만들어 낸다.

 

 - 함수를 합성하는 방법인 합성함수와 커링함수에 대해서 알아보자.

 

* 합성함수(Function Composition)

 - 함수 컴포지션은 흔히 우리가 이미 사용하고 있는 함수의 합성 방식이다.

 

 - 수학에서 g ◦ f = g(f(x)) 를 기억한다면 이와 같다고 생각해도 무방하다.

 

 - 위의 형식처럼 합성함수는 func1(func2(x)) 의 형태를 가지며, 함수가 1급함수이기 때문에 이런식으로 활용이 가능하다.

 

 - 1을 더하고 2의 제곱을 하는 코드를 살펴보자.

 

let x = 2;
const addOne = (n) => n + 1;
const powTwo = (n) => n ** 2;

const y = addOne(x);
const result = powTwo(y);

console.log(result); // 9

 

 - 위의 코드는 절차지향 스타일로 작성된 코드이다 . 위에서 y와 같은 변수는 사실상 result를 위한 값이므로 변수로 할당하는 것은 메모리 낭비라고 볼 수 있다.

 

 - 이를 변수를 제거하여 합성함수로 나타내면 다음과 같다.

 

const result = powTwo(addOne(x));

console.log(result); // 9

 

* 커링함수(Currying)

 - 커링은 매개변수를 모두 채우지 않은 형태로 함수를 남아있게하여 재활용이 가능하도록 한다.

 

 - 형태는 func(x)(y)(z) 처럼 나타난다. func(x, y, z) 를 매개변수를 하나만 사용할 수 있도록 바꾼 것이다.

 

 - 커링은 함수를 호출하는 것이 아니라 단지 변형시킬 뿐이다.

 

 - 아래의 코드는 배열의 원소에 num만큼 더하여 반환하는 예제이다.

 

const myArray1 = [1, 2, 3, 4, 5, 6];
const myArray2 = [2, 4, 6, 8, 10];
const myArray3 = [1, 3, 5, 7, 9];
const getAddArr1 = (num, arr) => {
  let newArr = [];
  arr.forEach((elem) => newArr.push(elem + num));
  return newArr;
};

console.log(getAddArr1(10, myArray1)); // [ 11, 12, 13, 14, 15, 16 ]
console.log(getAddArr1(10, myArray2)); // [ 12, 14, 16, 18, 20 ]
console.log(getAddArr1(10, myArray3)); // [ 11, 13, 15, 17, 19 ]

 

 - 위의 코드에서 모두 10을 더하고 있지만 반복해서 10을 입력하고 있다. 그렇다보니 코드가 지저분하다.

 

 - 커링을 사용해보자.

 

const getAddArr2 = (num) => (arr) => {
  let newArr = [];
  arr.forEach((elem) => newArr.push(elem + num));
  return newArr;
};
const addTwo = getAddArr2(10);
console.log(addTwo(myArray1)); // [ 11, 12, 13, 14, 15, 16 ]
console.log(addTwo(myArray2)); // [ 12, 14, 16, 18, 20 ]
console.log(addTwo(myArray3)); // [ 11, 13, 15, 17, 19 ]

 

 - 10이라는 num을 가진 함수를 미리 addTwo라는 이름으로 만들고 나중에 gettAddArr2(10)(arr) 형태로 재활용 했음을 볼 수 있다.

 

 - 아래는 두 개의 래퍼를 사용하여 제곱하는 함수를 구현한 예제이다.

 

function curry(f) {
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

function pow(a, b) {
  return a ** b;
}

let curriedPow = curry(pow);

console.log( curriedPow(3)(2) ); // 9

 

 - 처음 호출되면 curriedPow(3)의 인수 3이 Lexical Environment에 저장되고 새 래퍼 function(b)가 반환된다.

 

 - 새 래퍼는 2라는 인수로 호출되고 호출은 원본인 Pow에 전달된다.

 

 


 

참고

 

 

 

[프로그래밍] 함수형 프로그래밍(Functional Programming) 이란?

1. 함수형 프로그래밍(Functional Programming)에 대한 이해 [ 프로그래밍 패러다임(Programming Paradigm) ] 프로그래밍 패러다임(Programming Paradigm)은 프로그래머에게 프로그래밍의 관점을 갖게 하고 코드를..

mangkyu.tistory.com

 

 

함수형 프로그래밍에 관해

함수형 프로그래밍 알아보기

medium.com

 

 

[JAVA] 람다식(Lambda)의 개념 및 사용법

람다함수란? 람다 함수는 프로그래밍 언어에서 사용되는 개념으로 익명 함수(Anonymous functions)를 지칭하는 용어입니다. 현재 사용되고 있는 람다의 근간은 수학과 기초 컴퓨터과학 분야에서의 

khj93.tistory.com

 

 

JavaScript 관점 : 람다, 익명함수, 클로저

람다대수 특징 1. 람다 대수는 이름을 가질 필요가 없다. 2. 두개 이상의 입력이 있는 함수는 최종적으로 1개의 입력만 받는 람다 대수로 단순화 될 수 있다. 1번 특징에서 말하는 것은 익명함수이

centbin-dev.tistory.com

 

 

FP in JS (자바스크립트로 접해보는 함수형 프로그래밍) - 함수 컴포지션, 커링

두 번째 글입니다. 함수형 프로그래밍에서는 함수의 조합으로 원하는 값을 만들어 냅니다. 함수의 조합인 함수 컴포지션에 대해서 살펴보도록 하겠습니다. 그리고 커링 기법을 이용해 함수 컴

velog.io

 

 

Currying

 

javascript.info

 

 

 

'Computer Science > etc.' 카테고리의 다른 글

< OOP > 객체 지향 프로그래밍이란?  (0) 2021.06.12