함수형 프로그래밍

Eungae's avatar
Dec 10, 2025
함수형 프로그래밍

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

함수의 연속으로 프로그램을 구성하는 방식 (메서드 체이닝 Method Chaining)
int number = -12345; var result = number.abs().toString().contains('3'); print(result); // true
String word = 'abcd'; var index = word.toUpperCase().indexOf('B'); print(index); // 1
가변적인 데이터의 사용을 최소화하여 프로그램을 구성하는 방식.
→ 출력값이 항상 그 함수의 매개변수(입력값)에만 의존하도록.
int add(int a, int b) { return a + b; } void main() { int result = add(3, 4); print(result); // 7 }

순수 함수(Pure Function)

같은 입력값에 대하여 항상 같은 출력값이 나오는 함수
// 예시 int add(int a, int b) { return a + b; } int multiply(int a, int b) { return a * b; } int getTotal(List<int> numbers) { int result = 0; for (var number in numbers) { result += number; } return result; }
a, b는 입력값을 그대로 상수로 갖게 된다. ⇒ 부작용(Side Effect)가 발생할 가능성이 적다.
// 비고: 부작용이 발생할 수 있는 경우 int number = 0; void increaseNumber() { number += 1; } void main() { print(number); increaseNumber(); print(number); }
increaseNumber() 는 함수 외부의 값 number 를 변경하는 함수 ⇒ 명령형 프로그래밍 방식
cf. 입력값이 함수 내에서 변경되지 않고 상수처럼 처리되는 경우: 순수 함수
 
 

함수형 프로그래밍의 종류

형변환 함수 (Type Casting Function)

특정 타입의 데이터를 다른 타입의 데이터로 변환하는 함수
.toString() : 값을 String 타입으로 변환한 값을 반환
void main() { int number = 42; var result = number.toString(); print("$result, ${result.runtimeType}"); // 42, String }
void main() { double number = 1.5; var result = number.toString(); print("$result, ${result.runtimeType}"); // 1.5, String }
int.parse('') : Stringint로 변환한 값을 반환
void main() { String number = "123"; var result = int.parse(number); print("$result, ${result.runtimeType}"); // 123, int }
참고: int.tryParse('')
변수의 초기값이 숫자가 아닌 경우 int로 parse할 수 없음. ⇒ 오류 발생
void main() { String actor = "박정민"; var result = int.parse(actor); print(result); } // Uncaught Error, error: Error: FormatException: 박정민
int.tryParse('')는 변환에 실패할 경우 오류 대신 null을 반환.
void main() { String actor = "박정민"; var result = int.tryParse(actor); print(result); // null }
double.parse('') : Stringdouble로 변환한 값을 반환
변환이 안 될 경우 오류 발생
double.tryParse('') : 변환 불가 시 null 값 반환
.toList() : 특정 collection 타입의 값을 List 타입으로 변환한 값을 반환
void main() { Set<String> directorSet = {"박찬욱", "봉준호", "홍상수", "나홍진"}; var directorList = directorSet.toList(); print("$directorList, ${directorList.runtimeType}"); } // [박찬욱, 봉준호, 홍상수, 나홍진], List<String>
map 타입에는 적용할 수 없다. — Listset은 single 요소만 갖지만, map은 pair 요소를 갖기 때문에 변환 불가.
.toSet() : 특정 collection 타입의 값을 Set 타입으로 변환한 값을 반환
void main() { List<String> directorList = ["박찬욱", "봉준호", "홍상수", "나홍진"]; var directorSet = directorList.toSet(); print("$directorSet, ${directorSet.runtimeType}"); } // {박찬욱, 봉준호, 홍상수, 나홍진}, LinkedSet<String>
List에서 Set으로 변환 시 runtimeType은 왜 LinkedSet이 되는가?
GPT의 답을 요약하자면:
Set은 인터페이스(타입)이고 실제로 메모리에 저장되는 것은 구현체(클래스)란다.
  • 타입과 클래스의 차이… 아직도 헷갈려어어어어어ㅠㅠ
Set이 일종의 설계도 내지는 규약이라면, LinkedSet은 Set이 실제로 동작하도록 만든 구체적인 자료 구조!
List에서 갖고 있던 순서를 최대한 유지하면서 중복값을 삭제한 Set이라는 뜻이란다. 만약 순서가 유지되지 않은 Set이라면, List의 순서가 무작위로 변경되어서 중복값만 삭제하여 반환될 수도 있었는데, LinkedSet이라는 자료구조 덕분에 List의 순서를 최대한 유지하게 되는 것.
List의 순서를 유지한 Set이라고 해서, 실제로 index가 있는 것은 아니다.
.asMap() : 특정 collection 타입의 값을 Map 타입으로 변환한 값을 반환
void main() { List<String> directorList = ["박찬욱", "봉준호", "홍상수", "나홍진"]; var directorMap = directorList.asMap(); print(directorMap); } // {0: 박찬욱, 1: 봉준호, 2: 홍상수, 3: 나홍진}
Listindex 값이 Mapkey로, List의 값이 Mapvalue로 할당된다.
List의 값은 중복될 수 있지만, List의 index는 고유하기 때문에 → index가 key가 되는 것이 납득이 된다.
반면, Set은 index 값을 갖지 않으므로, Set.asMap은 불가능하다.
 

Insight or Questions.

함수는 원본의 값을 바꾸는 것이 아니라 변환된 값을 반환하는 것.

void main() { String n = "123"; int.parse(n); print(n.runtimeType); // String int m = 123; m.toString(); print(m.runtimeType); // int bool k = false; k.toString(); print(k.runtimeType); // bool }
원래는 이 다음 질문을 하기 위해서 이러한 코드를 작성했었는데, 결과값을 보니 형변환함수를 적용한 것이 무색하도록 원래의 type값이 출력되는 것이다. 엥?
이유는 간단했다. 함수는 원본의 값이 아니라, 함수에 따라 새로운 값을 반환하는 것.
나는 이 새로운 값을 지정하지 않은 것이다.
수정하자면 다음과 같다.
void main() { String n = "123"; int a = int.parse(n); print(a.runtimeType); // int int m = 123; String b = m.toString(); print(b.runtimeType); // String bool k = false; String c = k.toString(); print("$c, ${c.runtimeType}"); // false, String }
 
 

함수의 적용 방식 헷갈림.

함수 f(x)에 대하여 어떨 때는 x에 값을 넣고, 어떨 때는 .f(x) 즉 메서드 체이닝을 하는가?
void main() { String n = "123"; int.parse(n); print(n.runtimeType); // String int m = 123; m.toString(); print(m.runtimeType); // int }
위의 코드는 각각 intString 타입의 형변환 함수인데,
어떨 때는 객체를 괄호 안에 넣고 f(n), 어떨 때는 객체 뒤에 메서드 체이닝을 한다. m.f(x)
이건 어떤 차이 때문인가?
 
GPT에게 물어보니, 이건 객체 지향이라는 개념과 연관되어 있는 것으로 보인다.
일단 확실하지는 않지만 옮겨 적어두고 나중에 다시 봐야 할 듯.
“누가 변환 기능을 갖고 있느냐”의 차이 때문이야. 변환 기능이 타입(int, double, DateTime 같은 타입 자체)에 붙어 있으면 → int.parse(n) -- 정적 메서드 변환 기능이 객체(String, int 같은 인스턴스)에 붙어 있으면 → m.toString() -- 객체 지향 즉, 변환 로직이 타입에 붙어 있느냐 / 객체에 붙어 있느냐에 따라 문법이 갈린다. int.parse(n)은 “Stringint로 바꾸는 능력”이 int 타입 자체에 붙어 있다는 의미야. Dart는 문자열이 int로 변환될 수 있는지 여부를 int 타입이 알고 있다고 본다. 그래서 변환 함수가 객체(String)에 붙어 있는 게 아니라, 타입(int)에 붙어 있음. m.toString 의 경우는 정수값 123이라는 객체가 자신의 문자열 버전을 알고 있다. 이 구조는 “이 객체에게 서버 일을 시키는 문장”이야. 문법형식은 전형적인 OOP 방식: `객체.메서드()` Dart의 모든 객체는 기본적으로 Object를 상속하니까, toString()은 모든 객체가 갖는 메서드야. 형변환 메서드가 타입에 속하면 TypeName.method(value) 형태가 되고, 형변환 메서드가 객체에 속하면 object.method() 형태가 된다. 이것이 문법 차이를 만든다.
그러니까 이걸 이해하려면 객체 지향과 상속에 대한 이해가 필요하다.
커리큘럼 상 곧 나온다. 나중에 돌아와서 다시 봐야지.
 
Share article

나새끼메이커