-
함수형 프로그래밍이란?!💻 computer science/⚙️ 프로그래밍 2025. 7. 27. 21:41
프로그래밍 패러다임의 큰 흐름 속에서 함수형 프로그래밍이 무엇이며 왜 중요하고 어떤 특징을 가지는지 깊이 있게 살펴보겠습니다.
특히, 함수형 프로그래밍의 핵심 개념들을 다양한 예시와 함께 쉽게 이해할 수 있도록 구성했습니다.
🎯 프로그래밍 패러다임이란?

프로그래밍 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 해 주고 결정하는 역할을 한다. 예를 들어 객체지향 프로그래밍은 프로그래머들이 프로그램을 상호작용하는 객체들의 집합으로 볼 수 있게 하는 반면 함수형 프로그래밍은 상태값을 지니지 않는 함수값들의 연속으로 생각할 수 있게 해준다.
크게 보면 프로그래밍 패러다임은 선언형(Declarative) 과 명령형(Imperative) 두 가지로 나뉩니다.
1️⃣ 명령형 프로그래밍 (Imperative Programming) ✍️
명령형 프로그래밍은
HOW - 무엇을 어떻게 할 것인가에 집중하는 패러다임입니다.프로그램의 '상태' 와 그 상태를 '변경' 시키는 구문의 관점에서 연산을 설명하며 프로그램의 상태를 변경하는 방법을 명시적으로 지시하고
어떤 순서로 실행되어야 하는지를 중요하게 생각합니다.
- 하위 패러다임
- 절차형 프로그래밍
- 객체지향형 프로그래밍
예시: 요리 레시피 🍳
양파를 썰고, 프라이팬에 올리브유를 두르고 양파를 볶아라처럼 구체적인 순서를 명령하는 것과 같습니다.
특징
상태 변경(Mutable State) 변수의 값을 직접 변경하며 프로그램의 상태를 관리 제어 흐름(Control Flow) if, for, while 등 제어문을 통해 코드의 실행 순서를 명시적으로 제어 절차적(Procedural) 작업이 수행되는 '절차'에 초점을 맞춥니다. 2️⃣ 선언형 프로그래밍 (Declarative Programming) ✨
선언형 프로그래밍은
WHAT - 무엇을 할 것인가에 집중하는 패러다임입니다.프로그램이 달성해야 할 목표나 결과를 명시하고 그 목표를 달성하는 '방법'은 시스템 내부 또는 언어의 메커니즘에 맡깁니다.
- 하위 패러다임: 함수형 프로그래밍
예시:
맛있는 김치찌개를 만들어줘🍲- 구체적인 요리 과정은 언급하지 않고 결과만 선언하는 것과 같습니다.
특징
불변성(Immutability) 데이터의 변경을 피하고 변경이 필요할 경우 새로운 데이터를 생성하여 반환 부작용 없음(No Side Effects) 함수가 외부 상태를 변경하거나 외부 상태에 의존하지 않습니다. 표현력(Expressiveness) 코드가 '어떻게'보다 '무엇'을 하는지 명확하게 명시 📖 명령형과 선언형의 비교
특징 명령형 선언형 초점 어떻게(How) 상태를 변경할 것인가 무엇을(What) 달성할 것인가 관심 작업 수행 방법(알고리즘), 상태 변경 추적 원하는 정보가 무엇인지, 어떤 변환이 필요한지 상태 관리 변경 가능한 상태 (Mutable State) 불변성 (Immutability) - 값을 복사 실행 절차 중요도가 높다 중요도가 낮다 부작용 많음 (Side Effects) 없음 또는 최소화 (No Side Effects) 제어 흐름 반복문, 조건문, 함수 호출 재귀를 비롯한 함수 호출 사고방식 단계별 지시, 절차 결과 정의, 변환 ⭐ 함수형 프로그래밍 - 왜 필요하고 어떤 특징을 가질까? 🤔
함수형 프로그래밍은 선언형 프로그래밍의 한 종류로 '부수효과(Side-Effect)' 가 없는 순수한(Pure) 함수 기반의 프로그래밍 패러다임
🏭 예시로 이해하는 함수형 vs 비함수형: 부품을 조립하는 공장
👷♂️ 비함수형 공장
이 공장에서는 부품이 하나의 조립 라인을 따라 이동하며 각 작업자(함수)가 부품을 직접 가져와 작업하고 공유된 도구와 자재를 사용.
- 각 작업자는 전 단계에서 가공된 상태를 직접 수정하며 작업
- 어떤 작업자가 실수하면 그 결과가 다음 작업에 영향
✅ 특징
특징 예시 공유된 상태와 의존성 모든 작업자가 하나의 자재 창고를 공유 → 누군가가 재료를 소진하면 다른 작업자는 작업 불가 작업 순서 중요 + 부수효과 발생 순서를 잘못 지키면 오류 발생: 도색 전에 조립하는 경우, 부품 누락 디버깅이 어렵다 제품에 문제가 생기면 어느 과정에서 상태가 잘못되었는지 전체 추적 필요 👷♂️ 함수형 공장
각 작업자는 입력 부품을 받아 자신이 맡은 작업 이후 작업 내용을 반환.
- 작업자들은 공유 자재 창고나 상태에 의존하지 않고 자신의 작업에만 집중
✅ 특징
특징 예시 공유 상태 없이 순수한 작업 작업자는 입력만 받고 정해진 작업을 통해 작업된 내용만 반환 부수효과 없음 자신이 만든 작업 내용만 반환하고 다른 작업에는 영향을 주지 않는다 디버깅 각 작업자 단위로 “입력 → 출력”만 추적하면 원인 파악이 가능 💡 예제를 통해서 발견한 함수형 프로그래밍의 특징
특징 설명 인풋과 아웃풋이 있다!! 받은 작업 내용만 다루고 결과물만 반환 외부 상태에 의존하지 않는다 무엇을 참조하지 않고 자기 일만 함. 같은 입력엔 같은 출력 외부 영향을 안 받기 때문에 항상 같은 작업 내용에 대해서는 같은 결과물을 반환하는 결과 예측 가능 부작용 없음 결과물만 반환해 다른 것들에 영향을 주지 않음 🤔 함수형 프로그래밍이 필요한 이유: 부수효과(Side Effect) 관리
❗ 부수효과(side effect)란?
- 함수가 외부 상태를 변경하거나 참조하면서 발생하는 예측 불가능한 영향
- ex) 전역 변수 수정, 콘솔 출력, 파일 I/O 등
📍 Side Effect - 3가지 요소
액션 impure code, 부르는 시점에 의존하는 코드를 의미 계산 pure code, 파라미터 입력값을 계산해 반환하는 코드 데이터 액션과 계산에 필요한 재료 - 출처: 쏙쏙 들어오는 함수형 코딩(Grokking Simplicity, Eric Normand 저)
let test = 10; function sum(value) { test = test + value; return test; } console.log(sum(10)); // 20 console.log(sum(10)); // 30특히 멀티스레드 환경에서는 공유된 상태의 동시 접근 문제가 발생할 수 있습니다.
하나의 변수에 여러 스레드가 접근할 경우 동기화(lock) 가 필요하거나 언어/프레임워크에서 synchronized, mutex, lock 등의 키워드를 사용해야 합니다.
💡 함수형 프로그래밍은 공유 상태를 제거함으로써 이러한 문제를 방지합니다.
함수의 동작으로 인해 발생할 수 있는 변수의 값 변경을 배제하고 외부 변수를 사용하더라도 본체에 접근해 변경하는 방식이 아닌 인자로 넣어서 사본으로 복사해서 작업하기 때문에 문제가 발생하지 않습니다.
🤔 그럼 상태 변화 없이 고정된 값으로만 프로그램을 만들 수 있을까요?
❌ 아닙니다! 부품이 제품이 되지 않는 공장은 의미가 없듯이 모든 것을 함수형 프로그래밍 기준으로만 할 수는 없습니다.
⚠️ 이처럼 액션을 아예 하지 않을 수는 없고 액션을 줄이려면 어떻게 해야 하나?!?
- 바로 함수 안에서 외부 상태에 의존하지 않는 코드를 작성
- 일정 단위의 작업에 대해서는 부수효과 없이 안정적이면서 예측 가능한 프로그램을 만드는 것이 함수형 프로그래밍의 목표입니다!
✅ 함수형 프로그래밍의 특징
1️⃣ 선언형 프로그래밍
- "어떻게" 보다는 "무엇을" 중심으로 작성
- 명령형: 상태 변경과 로직 중심
- 선언형: 상태는 최소화하고, 순수 함수 조합 중심
2️⃣ 함수도 값이다 - 일급 객체 (First-class citizen)
함수형 프로그래밍에서는 함수도 값
- 변수에 담고, 인자로 전달하고, 반환값으로 사용 가능!
const output = input => console.log(input); ['안녕', '나는', 'beomsic임!'].forEach(output); // 출력: // 안녕 // 나는 // beomsic임!var calculate_double = function (number) { return number * 2; }; console.log(calculate_double); // 함수 자체로는 결과값이 나오지 않음. console.log(calculate_double(2)); // 인자로 숫자가 들어가야 결과값이 나옴 - 4- 이처럼 인자만 넣어주면 예측가능한 값을 반환하는 값 처럼 함수를 볼 수 있다!!
🥇 일급 객체
- 함수형 프로그래밍에서는 함수 가 일급 객체의 역할을 한다.
- 함수를 변수에 할당하거나
- 인자로 받거나(넘기거나)
- 함수의 반환 값으로 활용
3️⃣ 순수 함수
외부의 상태를 변경하지 않는 함수
- 오직 입력으로 들어오는 파라미터에만 의존합니다.
- 부수 효과들이 발생하지 않는 함수입니다.
- 외부 상태 참조나 변경이 없습니다.
4️⃣ 불변성
불변성은 데이터가 한 번 생성되면 그 상태를 절대 변경할 수 없다는 원칙입니다.
- 만약 데이터에 변화를 주고 싶다면 원본을 수정하는 대신 변경된 내용을 반영한 새로운 데이터의 복사본을 만들어 반환합니다.
- 어떤 값의 상태를 변경하지 않습니다.
5️⃣ 참조 투명성
동일한 인자에 대해 항상 동일한 결과를 반환하는 것을 의미
- 외부 변수에 의존 ❌
참조가 투명
동일한 인자에 대해 함수를 실행했을 때 어떠한 상태 변화없이 항상 동일한 결과를 반환해 결과를 (투명하게) 예측할 수 있다는 의미
👍 함수형 프로그래밍의 장점 및 활용 개념
1️⃣ 고차 함수 (Higher-order Function)
- 함수를 인자로 받거나 함수를 반환하는 함수.
- 재사용성, 추상화, 코드 유연성 증가.
var calculate = (num1, num2, op) => op(num1, num2); const add = (num1, num2) => num1 + num2; const multiply = (num1, num2) => num1 * num2; const power = (num1, num2) => Math.pow(num1, num2); const result1 = calculate(2, 3, add); const result2 = calculate(2, 3, multiply); const result3 = calculate(2, 3, power); console.log(result1); // 5 console.log(result2); // 6 console.log(result3); // 8 ------------------------------------------------- var calculate2 = op => num => op(2, num); const add2 = calculate2(add); const multiply2 = calculate2(multiply); const result4 = add2(3); const result5 = multiply2(3); console.log(result4); // 5 console.log(result5); // 62️⃣ 커링 (Currying)
Currying은 여러개의 인자를 가진 함수를 호출 할 경우 파라미터 수보다 적은 수의 파라미터를 인자로 받으면 누락된 파라미터를 인자로 받는 기법
- 여러 인자를 받는 함수를 부분 적용해서 필요한 인자만 먼저 넣고 나머지는 나중에 넣을 수 있음.
- 함수 재사용성과 조합성을 높임.
- first(3)처럼 미리 설정한 값을 재활용할 수 있음.
- 결과적으로 유연한 함수 조합과 코드 가독성 개선에 도움.
스칼라 코드
def sum(x: Int, y: Int): Int = x + y //sum: sum[](val x: Int,val y: Int) => Int sum(3, 4) //7 def first(x: Int): Int => Int = (y: Int) => x + y //first: first[](val x: Int) => Int => Int val second: (Int => Int) = first(3) //second: Int => Int = <function1> first(3) //Int => Int = <function1> first(3)(4) // 7 second(10) // 13 val ints = List(1,2,3,4) //ints: List[Int] = List(1, 2, 3, 4) val newInts1 = ints.map(first(5)) //newInts1: List[Int] = List(6, 7, 8, 9) val newInts2 = ints.map(second)- first(3) ⇒ 3 + y 를 반환
- second는 y를 입력받아서 3 + y 를 평가
- 3+4 가 평가되어 7을 반환
sum(x : Int, y : Int)
- sum(3, 4)처럼 여러개의 파라미터를 한번에 받습니다.
- 이런 메소드들을 first(3)(4)처럼 하나 씩 분리해서 받는 기법을 currying(커링) 이라 한다.
자바스크립트 사용 예제
function multiply(a) { return function (b) { return function (c) { return a * b * c; }; }; } var result = multiply(2)(3)(4); console.log(result);3️⃣ 함수 조합 / 컴비네이터 (Combinator)
- filter → slice → map → join 등 함수들을 연속적으로 조합하여 처리.
- 명령형 vs 선언형 스타일의 차이가 잘 드러남:
- 명령형: 상태 변경, 반복, 변수 선언 등 많은 코드 필요.
- 선언형: 원하는 동작을 간결하게 표현할 수 있음.
- 사이드 이펙트(부수 효과) 없이 순수한 데이터 변환만 이뤄짐 ⇒ 디버깅 및 테스트 용이.
class Student { constructor(name, major) { this.name = name; this.major = major; } } const students = [ new Student('beomsic', '컴퓨터'), new Student('JK', '컴퓨터'), new Student('홍길동', '화학'), new Student('범석', '컴퓨터'), new Student('리눅스', '컴퓨터'), new Student('공돌이', '기계'), new Student('유재석', '언론'), new Student('봉준호', '영화'), new Student('아이유', '음악'), ];- 학생들 배열에서 컴퓨터를 전공하는 3명을 선택해보기
⛔ 비함수형 코드
var filtered = []; for (var i = 0; i < students.length; i++) { if (students[i].major === '컴퓨터') { filtered.push(students[i]); } } filtered = filtered.splice(0, 3); // 3명만 선택 -> 부수효과 발생 var result = ''; for (var i = 0; i < filtered.length; i++) { result += `${filtered[i].name}(${filtered[i].major})`; } console.log(result); // beomsic(컴퓨터)JK(컴퓨터)범석(컴퓨터)✅ 함수형 코드
const result = students .filter(s => s.major === '컴퓨터') .slice(0, 3) .map(s => `${s.name}(${s.major})`) .join(''); console.log(result); // beomsic(컴퓨터)JK(컴퓨터)범석(컴퓨터)🔁 반복 대신 재귀 (Recursion)
함수형 프로그래밍에서 반복(loop) 대신 재귀(recursion) 를 사용하는 이유
이유 설명 상태 변경을 피하기 위해 반복문은 일반적으로 변수 값을 바꾸며 상태를 변경함 → 함수형은 불변성을 지향하므로 적합하지 않음 명령형보다 선언적인 표현을 위해 반복은 "어떻게" 수행할지를 말하지만, 재귀는 "무엇을" 할지를 표현 → 더 선언적이고 수학적 고차함수와 잘 어울림 함수형에선 map, reduce, filter 등이 기본 → 내부적으로는 모두 재귀 기반 함수 단위로 구성되는 자연스러운 구조 함수형 언어는 함수 조합을 중시 → 재귀는 함수로 반복을 표현하기에 자연스럽다 순수 함수로 표현 가능 재귀는 부작용(side effect) 없는 방식으로 연산을 수행하기 좋음 - 하위 패러다임