함수형 프로그래밍

혜빈·2024년 9월 15일

보충내용

목록 보기
29/38

기본 배경

  • 함수형 프로그래밍은 객체지향 프로그래밍과 마찬가지로, 프로그래밍 코드를 작성하는 하나의 방식
  • 객체지향 패러다임과 함수형 패러다임은 서로 충돌하는 것이 아니기 때문에 이 둘 모두를 적용해서 프로그래밍을 할 수 있음

절차지향적 프로그래밍

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let filteredArr = [];
let sum = 0;

for (let i = 0; i < arr.length; i++) {
  if (arr[i] % 2 === 0) {
    filteredArr.push(arr[i]);
    console.log(arr[i]);
  }
}

for (let i = 0; i < filteredArr.length; i++) {
  const squared = filteredArr[i] * filteredArr[i];
  console.log(squared);
  sum += squared;
}

console.log("Result: " + sum);
  • 여러 변수들이 사용되기 때문에 시점마다 달라질 수 있는 '상태'가 존재함
  • 변수들은 코드가 실행하는 작업에 영향을 주기도 하고, 작업으로부터 영향을 받기도 하기 때문에
    변수들이 사용되는 코드는 실행될 때마다 다른 결과를 반환하게 되고, 이를 예측하고 테스트 하는 일은 까다로울 수 있음
  • 또한, 변수들이 많이 사용되면 사용 과정에서 실수나 의도치 않은 값의 변경이 일어나는 등 문제의 소지가 많아짐
  • 멀티스레딩 과정에서는 하나의 변수에 여러 스레드가 동시에 접근함으로 인해 발생할 수 있는 문제들이 있고, 이를 방지하는 과정에서 병렬처리의 효율성이 감소하기도 함

함수형 프로그래밍

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

console.log(
  "Result: " +
  arr
  .filter(num => num % 2 === 0 && !console.log(num))
  .map(num => (console.log(num * num), num * num))
  .reduce((acc, num) => acc + num, 0)
);
  • 가능한 한 변수를 사용하지 않는 방식으로 코드를 작성함
  • 변수 없이 소프트웨어가 프로그래밍 되는 것은 불가능하지만, 함수형 코드에서 변수들은 이미 작성된 함수 안에 감춰져 있기 때문에 코드에 드러나지 않음
  • filter, map, reduce 안에도 변수를 사용하여 절차지향적으로 작성된 코드가 들어있음
  • 즉, 변수를 개발자가 직접 다루게 되는 것을 최소화 하는 것이 함수형 프로그래밍임

프로그래밍 언어 분류 (기준에 따라 다를 수 있음)

  1. 함수형 프로그래밍을 위해 만들어진 언어들
  • EX. Haskell, Clojure, Lisp, Scala, F#, Elm, Erlang 등
  1. 함수형 프로그래밍을 문제없이 구사할 수 있는 언어들
  • EX. JavaScript, Python, Ruby, C#, Swift, Kotlin, Rust 등
  1. 언어 자체적으로는 함수형 프로그래밍을 하기 까다로운 언어들
  • EX. C, C++, Java (-7), Fortran, Pascal, COBOL, BASIC, Visual Basic 등

  • 보통 우리가 사용하는 프로그래밍 언어들은 2번, 3번에 속함

  • 2번과 3번을 구분하는 기분은 함수의 일급 객체(First Class Object) 여부

일급 객체 (First Class Object)

  • 일급 객체란 프로그래밍 언어에서 다른 일반적인 데이터 타입(숫자, 문자열 등)과 동일하게 취급되는 객체를 의미
  • 즉, 일급 객체는 다른 데이터 타입들이 가지는 모든 특성과 기능을 동일하게 가질 수 있음
  • 변수에 할당될 수 있고, 배열 등에 담길 수 있고, 함수에 인자로 들어갈 수 있고, 반환값으로 나올 수 있고, 익명으로 만들어질 수도 있음

예시

JavaScript 코드

const add = func(a, b) {
	return a + b;
};
  • 함수를 변수나 상수에 값으로서 넣을 수 있음
  • 함수도 다른 원시값들이나 객체 등과 같은 방식으로 사용될 수 있는 데이터임

코드

public class Main {
	
    public static int add(int a, int b) {
    	return a + b;
    }
    
    public static void main(String[] arga) {
    }
}
  • Java 메소드는 JavaScript와 같이 사용할 수 없음
  • Java 메소드는 그 자체로 따로 만들어질 수 있는게 아니기 때문에
    Java에서 함수형 프로그래밍을 하려면 lambda 표현식과 Stream API 등의 우회적인 방법을 사용함

함수형 프로그래밍의 원칙

1. 순수 함수(pure function) 사용

funtion add(a, b) {
	return a + b;
}
  • 언제 실행되든 몇 번을 실행되든 같은 입력값에 대해 같은 결과값을 반환하는 함수
  • 위 add함수는 5, 6을 넣어서 실행하면 언제나 11을 반환함
  • 순수 함수는 매개변수로 주어지는 값 외에는 외부의 어떤 값에도 영향을 받거나 주지 않음
  • 바깥 일에 절대 관여하지 않기 때문에
    몇 번이 실행되어도 외부 데이터의 변화(부작용)를 일으키지 않음

2. 불편성의 원칙 ((Immutability)

  • 같은 이유로, 함수형 프로그래밍에서는 현존하는 데이터의 원본을 수정하지 않음
  • 기존 데이터 수정이 필요하다면 꼭 사본을 만든 후 거기에서 작업함
const nums = [1, 2, 3];
nums.push(4);
const newNums = [...nus, 4];
  • 만약 배열에 요소를 추가해서 사용해야 하는 경우
    첫 번째 코드와 같이push를 사용하면 원본 배열 자체가 수정되기 때문에
    두번째 코드처럼 추가 요소를 갖는 새 배열을 만들어서 사용하여 원본에 실행 흔적을 남기지 않음
const person = { name: 'been', age: 28 };
const updatePerson = {...person, age: 25};
  • 객체를 수정할 때도 사본을 만들어서 적용함
  • 이것을 불변성의 원칙(Immutability)이라고 함
  • 이처럼 실행의 흔적을 남기지 않기 때문에
    함수형 코드는 예측과 테스트가 용이하고 변경 이력 관리캐싱 같은 작업에 유리

3. 참조 투명성(Referential transparency)

  • 함수형 코드의 호출문을 그 반환값으로 대체할 수 있음
const add = (a, b)=> a + b;

const add_5_6 = add(5, 6);
console.log(add_5_6);

console.log(add_5_6 === add(5, 6));

console.log(
	add(add(5, 6), 5 === add(11, 5)
);
  
// 11
// true
// true
  • 위 코드에서 모든 add(5, 6)을 11로 변경해도 결과는 동일함
  • add 함수가 순수 함수이며, 그 함수의 실행이 외부에 끼치는 변화가 없기 때문에 가능함

4. 고차함수(Higher order function), 콜백함수(Callback function)

  • 함수형 프로그래밍이 실무에서 가장 널리 사용되는 형태
  • 다른 함수를 매개변수로 받거나 반환값으로 내놓는 함수
  • 반대의 개념으로, 다른 함수에 인자로 들어가는 함수를 콜백함수라고 함
  • 함수가 일급 객체인 언어들이기 때문에 가능함
function applyOperation(a, b, callback) {
	return callback(a, b);
}

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

const result = applyOperation(5, 6, add);

console.log(result);

// 11
  • applyOperation의 호출부를 보면 add 함수가 인자로 들어감
  • applyOperation 함수는 고차함수, add 함수는 콜백함수가 됨
setTimeout(
	() => { console.log("HiHi") }
  ,1000
);

const button = document.getElementById('myBtn');
button.addEventListener('click',
     function() {
		console.log('HiHi');
});
  • 위와 같은 코드들은 모두 콜백함수를 활용한 코드임
  • 1초 후에, 어떤 버튼이 클릭되었을 때 이 기능을 실행하라는 의미로 콜백함수를 인자로 넣어줌
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

console.log(
  "Result: " +
  arr
  .filter(num => num % 2 === 0)
  .map(num => num * num)
  .reduce((acc, num) => acc + num, 0)
);
  • 실무에서 '함수형으로 처리한다'는 말은 배열과 같은 컬렉션을 filter, map, reduce와 같은 고차 함수 메소드를 사용해서 다루는 것을 의미함
  • 이 고차 함수들은 인자로 주어진 콜백 함수들을 기준으로 컬렉션 안의 항목들을 처리함
  • filter : 주어진 콜백 함수가 true를 반환하는 값들만 통과
  • map : 각 값을 콜백 함수의 인자로 넣어 반환된 값으로 바꿈
  • reduce : 콜백 함수를 사용해서 항목들을 하나의 값으로 접어 나감

  • 현대적인 프로그래밍 언어들은 컬렉션을 이와 같이 함수형으로 처리할 수 있는 기능들을 대부분 제공함
  • Java와 같은 언어들에서도 Stream API와 같은 라이브러리를 사용해서 이와 같은 작업을 할 수 있음
  • 함수형 프로그래밍은 언어나 라이브러리가 복잡한 절차들이 프로그래밍 되어있는 기능을 제공하기 때문에 사용법들을 익혀두면 간단하게 함수형 코드를 작성할 수 있음
function compose (f,g) {
	return function (x) {
    	return f(g(x));
    }
}

const addOne = x => x + 1;
const double = x => x * 2;

const addOneTheDouble = compose(double, addOne);

console.log(
	addOneTheDouble(3)
);

// 8
  • compose 함수는 반환 값이 함수임
  • 두 개의 콜백 함수를 인자로 받아온 뒤, 주어진 값에 콜백 함수들을 차례로 모두 실행하는 함수를 내보냄
  • 주어진 값에 1을 더하는 addOne 함수와 주어진 값에 2를 곱하는 double 함수를 compose 함수에 콜백 함수로 넣으면
    주어진 값에 1을 더한 후 2를 곱하는 함수가 반환되고,
    그 함수에 3을 인자로 넣어 실행하면 8이 반환됨
  • 이처럼 둘 이상의 함수들을 조합하여 새로운 함수를 만들어내는 등의 정교한 작업을 고차함수를 사용해서 손쉽게 해낼 수 있음

참고

profile
최강 개발자를 꿈꾸는 병아리

0개의 댓글