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)을 적절히 조합하여 활용하는 것이 가장 효과적입니다. 함수형 프로그래밍의 장점을 적절히 활용하여 가독성 높은 코드 작성을 해보세요! 🚀
'강의노트 > [코드팩토리] [초급] Flutter 3.0 앱 개발' 카테고리의 다른 글
2강 Dart 객체지향 프로그래밍(OOP) (0) | 2025.03.19 |
---|---|
1강 Dart 기본기 (0) | 2025.03.18 |
댓글