클로저 & 람다
Vais는 일급 함수(first-class functions)를 지원하며, 간결한 람다 표현식과 강력한 클로저 기능을 제공합니다.
기본 문법
람다 표현식
람다는 |params| expr 형태로 작성합니다:
# 단일 표현식
double := |x| x * 2
result := double(5) # 10
# 여러 파라미터
add := |x, y| x + y
sum := add(3, 7) # 10
# 블록 형태
compute := |x, y| {
temp := x * 2
temp + y
}
타입 추론
대부분의 경우 타입을 명시할 필요가 없습니다:
# 자동 타입 추론
numbers := vec![1, 2, 3, 4, 5]
doubled := numbers.map(|x| x * 2)
# 명시적 타입 지정 (선택사항)
parse := |s: str| -> i64 {
# 문자열을 정수로 파싱
42
}
캡처 모드
Vais는 4가지 캡처 모드를 제공합니다:
1. ByValue (기본)
값을 복사하여 캡처합니다. 원본 변수는 영향받지 않습니다:
F main() -> i64 {
x := 10
# x를 값으로 캡처 (복사)
closure := |y| x + y
result := closure(5) # 15
# x는 여전히 10
result
}
2. Move 캡처
move 키워드를 사용하여 소유권을 클로저로 이전합니다:
F process_data() -> i64 {
data := 100
# data의 소유권을 클로저로 이전
consumer := move |multiplier| data * multiplier
result := consumer(2) # 200
# 이후 data 사용 불가 (소유권 이전됨)
result
}
주의: Move 캡처 후 원본 변수를 사용하면 컴파일 에러가 발생합니다.
3. ByRef 캡처
불변 참조로 캡처합니다. 캡처된 변수를 읽을 수 있지만 수정할 수 없습니다:
F main() -> i64 {
counter := 42
# 불변 참조로 캡처
reader := |&counter| counter + 1
value := reader() # 43
# counter는 여전히 42 (수정 불가)
counter
}
구현 상태: ByRef 캡처는 Phase 42에서 완전히 구현되었습니다. 캡처된 변수는 불변 참조로 전달됩니다.
4. ByMutRef 캡처
가변 참조로 캡처합니다. 클로저 내부에서 캡처된 변수를 수정할 수 있습니다:
F main() -> i64 {
counter := mut 0
# 가변 참조로 캡처
increment := |&mut counter| {
counter = counter + 1
counter
}
increment() # counter = 1
increment() # counter = 2
counter # 2
}
구현 상태: ByMutRef 캡처는 Phase 42에서 완전히 구현되었습니다. 캡처된 변수는 가변 참조로 전달되어 클로저 내에서 수정이 가능합니다.
고차 함수
클로저를 함수 파라미터로 전달할 수 있습니다:
# 고차 함수: 함수를 파라미터로 받음
F apply_twice(f, x: i64) -> i64 {
temp := f(x)
f(temp)
}
F main() -> i64 {
double := |n| n * 2
result := apply_twice(double, 3) # ((3 * 2) * 2) = 12
result
}
컬렉션과 함께 사용
F main() -> i64 {
numbers := vec![1, 2, 3, 4, 5]
# map: 각 요소에 함수 적용
doubled := numbers.map(|x| x * 2)
# [2, 4, 6, 8, 10]
# filter: 조건을 만족하는 요소만 선택
evens := numbers.filter(|x| x % 2 == 0)
# [2, 4]
# reduce: 누적 연산
sum := numbers.fold(0, |acc, x| acc + x)
# 15
sum
}
클로저 체이닝
여러 고차 함수를 연쇄적으로 사용할 수 있습니다:
F main() -> i64 {
result := vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
.filter(|x| x % 2 == 0) # 짝수만
.map(|x| x * x) # 제곱
.fold(0, |acc, x| acc + x) # 합계
# (2² + 4² + 6² + 8² + 10²) = 220
result
}
실전 예제
커링(Currying)
F make_adder(n: i64) {
|x| n + x
}
F main() -> i64 {
add_10 := make_adder(10)
result1 := add_10(5) # 15
result2 := add_10(20) # 30
result1 + result2 # 45
}
콜백 패턴
F process_async(data: i64, callback) -> i64 {
# 비동기 처리 시뮬레이션
processed := data * 2
callback(processed)
}
F main() -> i64 {
result := process_async(21, |x| {
# 결과 처리
x + 10
})
result # 52
}
지연 실행
F defer_execution(action) -> i64 {
# 일부 초기화 작업
init := 100
# 나중에 action 실행
action(init)
}
F main() -> i64 {
defer_execution(|value| {
# 지연된 작업
value * 3
}) # 300
}
성능 고려사항
- 인라이닝: 작은 람다는 컴파일러가 자동으로 인라인 최적화합니다.
- 클로저 크기: 캡처하는 변수가 많을수록 클로저 객체가 커집니다.
- Move vs Copy: 큰 데이터 구조는 move 캡처를 사용하여 불필요한 복사를 피하세요.
제약사항
-
참조 캡처: ByRef(
|&x|)와 ByMutRef(|&mut x|) 캡처는 포인터 전달 방식으로 구현되어 있습니다. -
재귀 람다: 람다는 자기 자신을 직접 호출할 수 없습니다. 재귀가 필요한 경우 일반 함수를 사용하세요.
-
타입 추론 한계: 복잡한 경우 타입을 명시해야 할 수 있습니다.
요약
- 기본 문법:
|params| expr또는|params| { body } - 4가지 캡처 모드: ByValue(기본), Move, ByRef, ByMutRef
- 타입 추론: 대부분 자동, 필요시 명시 가능
- 고차 함수: 함수를 파라미터로 전달하거나 반환 가능
- 실용적 패턴: 커링, 콜백, 지연 실행 등
클로저는 Vais의 함수형 프로그래밍 기능의 핵심이며, 간결하고 표현력 있는 코드를 작성하는 데 필수적인 도구입니다.