본문 바로가기
강의노트/[코드팩토리] [초급] Flutter 3.0 앱 개발

3강 함수형 프로그래밍 (FP)

by 오근성 2025. 3. 19.
728x90

1. 함수형 프로그래밍(Functional Programming) 개념과 활용

함수형 프로그래밍(Functional Programming, FP)은 프로그래밍 패러다임 중 하나로,

함수형 프로그래밍은 코드를 보다 예측 가능하고 유지보수하기 쉽게 만드는 패러다임입니다. 이를 이해하기 위해 몇 가지 핵심 개념을 알아보겠습니다.

 

 


a. 순수 함수 (Pure Function)

순수 함수는 같은 입력에 대해 항상 같은 출력을 반환하는 함수입니다. 외부 상태를 변경하지 않으며(부작용이 없음), 함수 실행 결과가 항상 동일해야 합니다.

✅ 순수 함수 예제

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

void main() {
  print(add(3, 5));  // 항상 8을 반환
  print(add(3, 5));  // 항상 8을 반환
}

위 함수는 같은 입력 (3,5)에 대해 항상 8을 반환하므로 순수 함수입니다.

❌ 순수하지 않은 함수의 예제

int total = 0;  // 외부 상태

int addToTotal(int value) {
  total += value;  // 외부 상태 변경
  return total;
}

void main() {
  print(addToTotal(5));  // 5
  print(addToTotal(5));  // 10 (결과가 달라짐)
}

이 함수는 외부 변수 total을 변경하기 때문에 순수 함수가 아닙니다.


b. 불변성 (Immutability)

데이터를 직접 변경하지 않고, 변경이 필요한 경우 새로운 데이터를 생성하는 것이 함수형 프로그래밍의 핵심 원칙입니다.

✅ 불변성을 유지하는 예제

List<int> numbers = [1, 2, 3];

List<int> addNumber(List<int> list, int newNumber) {
  return [...list, newNumber];  // 기존 리스트를 변경하지 않고 새로운 리스트 반환
}

void main() {
  List<int> newNumbers = addNumber(numbers, 4);
  print(numbers);      // [1, 2, 3] (원본 유지)
  print(newNumbers);   // [1, 2, 3, 4] (새로운 리스트)
}

❌ 불변성을 깨뜨리는 코드 (비추천)

void addNumberDirectly(List<int> list, int newNumber) {
  list.add(newNumber);  // 원본 리스트 직접 수정
}

void main() {
  List<int> numbers = [1, 2, 3];
  addNumberDirectly(numbers, 4);
  print(numbers);  // [1, 2, 3, 4] (원본이 변해버림)
}

위 코드에서는 기존 리스트 numbers를 직접 변경했기 때문에 불변성을 유지하지 못합니다.


c. 고차 함수 (Higher-Order Function)

고차 함수란 함수를 매개변수로 받거나, 함수를 반환하는 함수를 의미합니다. 대표적인 예로 map(), where(), reduce(), fold() 등이 있습니다.

✅ map()을 이용한 예제

List<String> names = ["Alice", "Bob", "Charlie"];

List<String> addPrefix(List<String> list, String prefix) {
  return list.map((name) => "$prefix$name").toList();
}

void main() {
  List<String> newNames = addPrefix(names, "Hello, ");
  print(newNames);  // ["Hello, Alice", "Hello, Bob", "Hello, Charlie"]
}

✅ where()을 이용한 필터링 예제

List<int> numbers = [1, 2, 3, 4, 5, 6];

List<int> getEvenNumbers(List<int> list) {
  return list.where((number) => number.isEven).toList();
}

void main() {
  List<int> evenNumbers = getEvenNumbers(numbers);
  print(evenNumbers);  // [2, 4, 6]
}

✅ reduce()를 이용한 리스트 값 합산 예제

List<int> numbers = [1, 2, 3, 4, 5];

int sum(List<int> list) {
  return list.reduce((prev, next) => prev + next);
}

void main() {
  print(sum(numbers));  // 15
}

✅ fold()를 이용한 리스트 길이 세기

List<String> words = ["Hello", "Functional", "Programming"];

int countCharacters(List<String> list) {
  return list.fold(0, (prev, next) => prev + next.length);
}

void main() {
  print(countCharacters(words));  // 24 (각 단어의 문자 수 합산)
}

d. 함수형 프로그래밍의 장점

순수 함수 → 코드의 예측 가능성이 높아짐
불변성 → 원본 데이터 보호, 예기치 않은 오류 방지
고차 함수 → 코드의 재사용성을 높이고 가독성을 향상

함수형 프로그래밍을 적극 활용하면 코드가 더욱 간결하고 유지보수가 쉬워집니다. 😃🚀

 

 

 

2. 컬렉션 변환: 리스트, 맵, 세트

📌 리스트(List) 선언

List<String> blackpink = ['로제', '지수', '리사', '제니'];
print(blackpink);

출력:

[로제, 지수, 리사, 제니]

📌 리스트 → 맵(Map) 변환

리스트를 맵으로 변환할 때는 **인덱스를 키(key)**로 사용하고, **리스트의 요소를 값(value)**으로 변환할 수 있습니다.

var blackpinkMap = blackpink.asMap();
print(blackpinkMap);

출력:

{0: 로제, 1: 지수, 2: 리사, 3: 제니}

📌 리스트 → 세트(Set) 변환

세트는 중복을 허용하지 않는 컬렉션이므로, 같은 요소가 여러 개 있을 경우 자동으로 하나만 유지됩니다.

var blackpinkSet = blackpink.toSet();
print(blackpinkSet);

출력:

{로제, 지수, 리사, 제니}

중복 요소가 있을 경우:

List<String> blackpinkDuplicates = ['로제', '지수', '리사', '제니', '제니'];
var uniqueSet = blackpinkDuplicates.toSet();
print(uniqueSet);

출력:

{로제, 지수, 리사, 제니}  // 제니가 하나만 유지됨

📌 맵 → 리스트 변환

맵의 키(keys) 또는 값(values)만 리스트로 변환할 수 있습니다.

print(blackpinkMap.keys.toList());   // [0, 1, 2, 3]
print(blackpinkMap.values.toList()); // [로제, 지수, 리사, 제니]

3. 데이터 변환과 고차 함수 활용

📌 map()을 활용한 변환

map() 함수는 리스트의 각 요소를 변환할 때 사용됩니다. 원본 리스트를 변경하지 않고, 변환된 데이터를 포함한 새로운 리스트를 반환합니다.

var newBlackpink = blackpink.map((member) => 'BLACKPINK $member').toList();
print(newBlackpink);

출력:

[BLACKPINK 로제, BLACKPINK 지수, BLACKPINK 리사, BLACKPINK 제니]

📌 where()을 활용한 필터링

where() 함수는 특정 조건을 만족하는 요소만 필터링하는 데 사용됩니다.

var filtered = blackpink.where((member) => member.startsWith('지')).toList();
print(filtered);

출력:

[지수]

📌 reduce()를 활용한 값 누적

reduce() 함수는 리스트의 요소를 하나씩 처리하며 누적 연산을 수행합니다.

List<int> numbers = [1, 3, 5, 7, 9];
var sum = numbers.reduce((prev, next) => prev + next);
print(sum);

출력:

25

📌 fold()를 활용한 누적 연산 (초기값 지정 가능)

fold()는 reduce()와 유사하지만, 초기값을 설정할 수 있는 차이점이 있습니다.

var sumWithInitial = numbers.fold(10, (prev, next) => prev + next);
print(sumWithInitial);

출력:

35  // (10 + 1 + 3 + 5 + 7 + 9)

4. 실제 활용 예제: JSON 데이터를 객체로 변환하기

실제 프로젝트에서는 API 응답 데이터를 클래스로 변환하는 경우가 많습니다.

📌 Map 데이터를 Class 객체로 변환

class Person {
  final String name;
  final String group;

  Person({required this.name, required this.group});

  @override
  String toString() => 'Person(name: $name, group: $group)';
}

List<Map<String, String>> peopleData = [
  {'name': '로제', 'group': 'BLACKPINK'},
  {'name': 'RM', 'group': 'BTS'}
];

List<Person> people = peopleData.map((data) => Person(
  name: data['name']!,
  group: data['group']!,
)).toList();

print(people);

출력:

[Person(name: 로제, group: BLACKPINK), Person(name: RM, group: BTS)]

5. 결론

함수형 프로그래밍은 데이터 변환과 조작을 간결하고 직관적으로 수행할 수 있도록 도와주는 패러다임입니다. map(), where(), reduce(), fold()와 같은 고차 함수들을 활용하면 코드를 더 깔끔하고 효율적으로 작성할 수 있습니다. 또한, 불변성을 유지하면서 새로운 데이터를 생성하는 방식은 코드의 안정성과 예측 가능성을 높이는 데 큰 도움이 됩니다.

실제 개발에서는 객체지향 프로그래밍(OOP)과 함수형 프로그래밍(FP)을 적절히 조합하여 활용하는 것이 가장 효과적입니다. 함수형 프로그래밍의 장점을 적절히 활용하여 가독성 높은 코드 작성을 해보세요! 🚀

728x90

댓글