함수형 프로그래밍(Functional Programming)

나나콘·2024년 5월 7일
post-thumbnail

프로그래밍 패러다임(Programming Paradigm)

프로그래머에게 프로그래밍의 관점을 갖게하고 코드를 어떻게 작성할 지 결정하는 역할을 한다.

최근의 프로그래밍 패러다임은 2가지로 구분된다.

  1. 명령형 프로그래밍

    무엇(What)을 할 것인지보다 어떻게(How) 할 것인지를 설명하는 방식

    - 절차지향 프로그래밍 (C, C++)
    - 객체지향 프로그래밍 (C++, Java, C#)
  2. 선언형 프로그래밍

    어떻게(How) 할것인지보다 무엇(What)을 할 것인지 설명하는 방식

  • 함수형 프로그래밍 : 순수 함수를 조합하고 소프트웨어를 만드는 방식(클로저, 하스켈, 리스프)

명령형 프로그래밍으로 개발한 소프트웨어의 크기가 커짐에 따라, 스파게티 코드를 유지보수하는 것이 힘들다.
함수형 프로그래밍은 거의 모든 것을 순수 함수로 나누어 문제를 해결하는 기법으로
작은 문제를 해결하기 위해 함수를 작성하여 가독성을 높이고 유지보수를 용이하게 해준다

Clean Code의 저자는 함수형 프로그래밍을 대입문이 없는 프로그래밍이라고 하였다.


함수형 프로그래밍에 대한 이해

  1. 대입문을 사용하지 않는 프로그래밍
  2. 작은 문제를 해결하기 위한 함수를 작성

명령형 프로그래밍

for(int i = 1; i < 10; i++) {
	Systemout.println(i);
}

함수형 프로그래밍

// 첫번째 인자 : 몇까지 'iteration'을 돌 것인가를 매개변수로 받는다.
// 두번째 인자 : 출력하라는 '함수'를 매개변수로 받고 있다.
process(10, print(num));

함수형 프로그래밍무엇을(What)에 포커스를 두는 프로그래밍이라고 하였습니다.
그래서 출력을 하는 함수를 파라미터로 넘길 수 있습니다.

명령형 프로그래밍에서는 메소드를 호출하면 상황에 따라 내부의 값이 바뀔 수 있다.
즉, 우리가 개발한 함수 내에서 선언된 변수의 메모리에 할당된 값이 바뀌는 등의 변화가 발생할 수 있다.
하지만 함수형 프로그래밍에서는 대입문이 없기 때문에 메모리에 한 번 할당된 값은 새로운 값으로 변할 수 없다.

함수형 프로그래밍의 특징

부수 효과가 없는 순수 함수1급 객체로 간주하여 파라미터나 반환값으로 사용할 수 있으며, 참조 투명성을 지킬 수 있다.

🔔 부수효과(Side Effect)

  • 변수의 값이 변경됨
  • 자료 구조를 제자리에서 수정함
  • 객체의 필드값을 설정함
  • 예외나 오류가 발생하여 실행이 중단됨
  • 콘솔 또는 파일 I/O가 발생함

🔔 순수 함수(Pure Function)

: 부수 효과(Side Effect)를 제거한 함수

  • Memory or I/O 관점에서 Side Effect가 없는 함수
  • 함수의 실행이 외부에 영향을 끼치지 않는 함수

순수 함수의 장점

  • 함수 자체가 독립적이다.
  • Side Effect가 없어 Thread에 안전성을 보장 받을 수 있다.
  • Thread에 안정성을 보장받아 병렬 처리를 동기화 없이 진행할 수 있다.

🔔 1급 객체

  • 변수나 데이터 구조 안에 담을 수 있다.
  • 함수를 다른 함수의 파라미터로 전달 할 수 있다.
  • 반환값으로 사용 할 수 있다.
  • 할당에 사용된 이름과 무관하게 고유한 구별이 가능하다.

함수형 프로그래밍에서 함수는 1급 객체로 취급받기 때문에 위의 에제에서 본 것처럼 함수를 파라미터로 넘기는 등의 작업이 가능한 것이다.

🔔 참조 투명성(Referential Transparency)

  • 동일한 인자에 대해 항상 동일한 결과를 반환해야 한다.
  • 참조 투명성을 통해 기존의 값은 변경되지 않고 유지된다.

함수형 프로그래밍의 핵심 동기
: 부작용을 제거하여 프로그램의 동작을 이해하고 예측을 용이하게 하는 것


Javascript를 통한 예시

예제 및 디자인 패턴

명령형 프로그래밍

// 30세 이상인 user
const users = [
  { name: 'na', age: 20 },
  { name: 'kim', age: 40 }
];
let over_30_users = [];
for(let i = 0; i < users.length; i++) {
  if(users[i].age >= 30) {
  	over_30_users.push(users[i]);   
  }
}
console.log(over_30_users); // [{"name": "kim", "age": 40}]

함수형 프로그래밍

const users = [
  { name: 'na', age: 20 },
  { name: 'kim', age: 40 }
];
const over_30_cond = (user) => {
    return user.age > 30
};
const over_30_users = (users, over_30_cond) => {
    return users.filter(over_30_cond);
}
over_30_users(users, over_30_cond);  // [{"name": "kim", "age": 40}]

대표적인 함수형 프로그래밍 함수들이 있습니다.

  • 반복문 대신에 map, filter, reduce, all, any, find
let arr = [1, 2, 3, 4, 5];
let map = arr.map(function(x) {
  return x * 2;
}); // [2, 4, 6, 8, 10]

입력 : 처음에 배열(arr)을 넣는다.
출력 : 결과(map)을 얻었습니다.

arr이 사용은 됐지만, 값은 변하지 않았고
map이라는 결과를 내고 아무런 부작용(side effect)도 일어나지 않았습니다.

바로 이런게 함수형 프로그래밍에 적합한 함수, 순수함수 입니다.


let arr = [1, 2, 3, 4, 5];
let condition = function(x) { 
  return x % 2 === 0;
}
let ex = function(array) {
  return array.filter(condition);
};
ex(arr); // [2, 4]

ex 함수는 순수함수 일까요?
아닙니다. 인자로 받지 않은 condition 변수를 사용하였습니다.

let arr = [1, 2, 3, 4, 5];
let condition = function(x) { 
  return x % 2 === 0;
}
let ex = function(array, cond) {
  return array.filter(cond);
};
ex(arr); // [2, 4]

이렇게 condition 변수도 인자로 받으면 됩니다.
이렇게 하면 쉽게 에러를 추적할 수 있습니다.

  1. parmeter가 문제
  2. 함수 내부가 문제

둘 중 하나이기 때문입니다.


반복문을 사용하지 말라고 했는데 그렇다면 어떻게 처리해야할까요

1부터 10까지 더할때

let num = 0;
for (let i = 1; i <= 10; i++) {
  sum += i;
}

보통 이렇게 하지만 함수형 프로그래밍으로 바꾸면

function add(sum, count) {
  sum += count;
  if(count > 0) {
    return add(sum, count - 1);
  } else {
    return sum;
  }
}
add(0, 10); // 55

add 안에서 add를 또 호출합니다.
이렇게 함수형으로 표현하면 장점이, 코드의 재사용성이 매우 높아집니다.
한 번 함수로 만들어 놓으면 언제든지 add 함수를 다시 쓸 수 있습니다.

reduce 메소드를 사용하여 좀 더 함수형 프로그래밍 답게 사용해봅시다.

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.reduce((prev, cur) => {
    return prev + cur;
}); // 55

함수형 프로그래밍 vs 객체지향 프로그래밍

함수형 프로그래밍

🏃‍♂️ 동사 중심 - (What 무엇을 원하는지) - 함수 기준

moveLeft(penguin);
moveLeft(dog);
swim(penguin, fast);

객체지향 프로그래밍

🐈 명사 중심 - 데이터/객체 기준

penguin.moveLeft();
penguin.swim();
dog.moveLeft({x: -5, y: 2});
dog.run();

함수형 컴포넌트 vs 클래스형 컴포넌트 (React)

🏃‍♂️ 함수형 컴포넌트

  • 클래스형 컴포넌트보다 선언하기 편하다.
  • 메모리 자원을 덜 사용한다.
  • state와 LifeCycle API를 사용할 수 없다는 단점은 React Hooks이 도입되면서 해결 ex) useState
// 함수형
import React from 'react';

function App() {
  const name = '리액트';
  return <div>{name}</div>;
}

export default App;

🐈 클래스형 컴포넌트

  • state 기능 및 LifeCycle 기능을 사용할 수 있으며 임의 메소드를 정의할 수 있다.
  • render 함수가 꼭 있어야 하고, 그 안에 보여 주어야 할 JSX를 반환해야 한다.
  • 오래된 프로젝트는 대부분 class형으로 되어있어 유지보수를 위해서 알아두어야 할 필요가 있다.
// 클래스형
import React, { Component } from 'react';

class App extends Component {
  render() {
    const name = '리액트';
    return <div>{name}</div>;
  }
}

export default App;

❓❓ 왜 클래스형에서 함수형으로 넘어갔을까?

현재 공식문서에서 함수형 컴포넌트와 Hook 사용 권장

  • Hook을 사용하면 컴포넌트로부터 상태 관련 로직을 추상화 할 수 있다. => 독립적인 테스트와 재사용 가능
  • Hook은 계층 변화 없이 상태 관련 로직을 재사용할 수 있어 많은 컴포넌트들이 공유하기 쉬워지는 역할을 한다.
  • Funcinal 컴포넌트가 가독성이 뛰어난다.

💫 결론

함수형 프로그래밍을 활용하면

  • 가독성, 재사용성, 높은 조합성, 쉬운 에러추적 이 가능하다.

📘 References

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

함수형 프로그래밍(Functional Programming)

함수형 프로그래밍과 클래스형 프로그래밍

profile
지식을 기록하고, 경험을 코드로 남겨라.

0개의 댓글