토파즈는 강력한 함수형 프로그래밍 라이브러리를 제공하여 선언적이고 표현력 있는 코드 작성을 지원합니다. 모든 함수형 도구들을 활용해 보세요! 🚀
🎯 고차 함수 (Higher-Order Functions)
기본 고차함수
// 함수를 인자로 받는 함수
function applyTwice<T>(함수: (T) -> T, 값: T) -> T {
return 함수(함수(값))
}
let 두배 = (x: int) => x * 2
let 결과 = applyTwice(두배, 5) // 20
// 함수를 반환하는 함수
function multiplier(배수: int) -> (int) -> int {
return (값: int) => 값 * 배수
}
let 삼배함수 = multiplier(3)
let 삼배결과 = 삼배함수(7) // 21
// 조건부 함수 적용
function conditionalApply<T>(
조건: (T) -> bool,
함수: (T) -> T,
값: T
) -> T {
if 조건(값) {
return 함수(값)
} else {
return 값
}
}
let 짝수면두배 = conditionalApply(
x => x % 2 == 0,
x => x * 2,
8
) // 16
print("결과: {결과}")
print("삼배 결과: {삼배결과}")
print("짝수면 두배: {짝수면두배}")
함수 조합 (Function Composition)
// 기본 함수 조합
function compose<A, B, C>(
f: (B) -> C,
g: (A) -> B
) -> (A) -> C {
return (입력: A) => f(g(입력))
}
let 더하기1 = (x: int) => x + 1
let 곱하기2 = (x: int) => x * 2
let 조합함수 = compose(곱하기2, 더하기1) // (x + 1) * 2
let 조합결과 = 조합함수(5) // 12
// 다중 함수 조합
function pipe<T>(...함수들: Array<(T) -> T>) -> (T) -> T {
return (초기값: T) => {
return 함수들.reduce((누적, 현재함수) => 현재함수(누적), 초기값)
}
}
let 복합처리 = pipe(
(x: int) => x + 1, // +1
(x: int) => x * 2, // *2
(x: int) => x - 3, // -3
(x: int) => x / 2 // /2
)
let 파이프결과 = 복합처리(10) // 9.5
print("조합 결과: {조합결과}")
print("파이프 결과: {파이프결과}")
부분 적용 (Partial Application)
// 부분 적용 함수
function partial<A, B, C>(
함수: (A, B) -> C,
첫번째인자: A
) -> (B) -> C {
return (두번째인자: B) => 함수(첫번째인자, 두번째인자)
}
let 더하기 = (a: int, b: int) => a + b
let 10더하기 = partial(더하기, 10)
let 부분적용결과 = 10더하기(5) // 15
// 다중 인자 부분 적용
function partial3<A, B, C, D>(
함수: (A, B, C) -> D,
첫번째: A,
두번째: B
) -> (C) -> D {
return (세번째: C) => 함수(첫번째, 두번째, 세번째)
}
let 계산 = (x: int, y: int, z: int) => x * y + z
let 부분계산 = partial3(계산, 2, 3) // 2 * 3 + z
let 최종결과 = 부분계산(4) // 10
print("부분 적용 결과: {부분적용결과}")
print("최종 결과: {최종결과}")
🔗 커링 (Currying)
기본 커링
// 수동 커링
function curry2<A, B, C>(함수: (A, B) -> C) -> (A) -> (B) -> C {
return (a: A) => (b: B) => 함수(a, b)
}
function curry3<A, B, C, D>(함수: (A, B, C) -> D) -> (A) -> (B) -> (C) -> D {
return (a: A) => (b: B) => (c: C) => 함수(a, b, c)
}
// 커링 적용
let 더하기 = (x: int, y: int) => x + y
let 커링된더하기 = curry2(더하기)
let 5더하기 = 커링된더하기(5)
let 커링결과 = 5더하기(3) // 8
// 3개 인자 커링
let 계산식 = (x: int, y: int, z: int) => x * y + z
let 커링된계산식 = curry3(계산식)
let 단계별계산 = 커링된계산식(2)(3)(4) // 10
print("커링 결과: {커링결과}")
print("단계별 계산: {단계별계산}")
자동 커링
// 자동 커링 데코레이터
function autoCurry<T>(함수: Function) -> Function {
// 런타임에서 인자 개수를 확인하고 자동으로 커링
return function(...인자들: Array<T>) {
if 인자들.length >= 함수.arity {
return 함수.apply(null, 인자들)
} else {
return function(...추가인자들: Array<T>) {
return autoCurry(함수).apply(null, [...인자들, ...추가인자들])
}
}
}
}
// 사용 예제
let 곱셈 = autoCurry((a: int, b: int, c: int) => a * b * c)
let 방법1 = 곱셈(2, 3, 4) // 24 (한번에 모든 인자)
let 방법2 = 곱셈(2)(3)(4) // 24 (순차적 적용)
let 방법3 = 곱셈(2, 3)(4) // 24 (부분 적용)
let 부분곱셈 = 곱셈(2, 3) // 함수 반환
let 완성곱셈 = 부분곱셈(4) // 24
print("자동 커링 결과들: {방법1}, {방법2}, {방법3}")
🔄 함수 변환 유틸리티
메모이제이션 (Memoization)
// 기본 메모이제이션
function memoize<T, R>(함수: (T) -> R) -> (T) -> R {
let mut 캐시: Map<T, R> = Map.new()
return (입력: T) => {
if 캐시.has(입력) {
return 캐시.get(입력).unwrap()
}
let 결과 = 함수(입력)
캐시.set(입력, 결과)
return 결과
}
}
// 피보나치 메모이제이션
let 피보나치 = memoize((n: int) => {
print("계산: 피보나치({n})")
if n <= 1 { return n }
return 피보나치(n - 1) + 피보나치(n - 2)
})
let 피보10 = 피보나치(10) // 55 (중복 계산 없음)
let 피보10재호출 = 피보나치(10) // 55 (캐시에서 즉시 반환)
// TTL 메모이제이션 (Time To Live)
function memoizeWithTTL<T, R>(
함수: (T) -> R,
만료시간: int
) -> (T) -> R {
let mut 캐시: Map<T, { 결과: R, 시간: int }> = Map.new()
return (입력: T) => {
let 현재시간 = Date.now()
if 캐시.has(입력) {
let 캐시항목 = 캐시.get(입력).unwrap()
if (현재시간 - 캐시항목.시간) < 만료시간 {
return 캐시항목.결과
}
}
let 결과 = 함수(입력)
캐시.set(입력, { 결과, 시간: 현재시간 })
return 결과
}
}
print("피보나치 10: {피보10}")
디바운싱과 스로틀링
// 디바운스 함수
function debounce<T>(
함수: (...T) -> void,
지연시간: int
) -> (...T) -> void {
let mut 타이머ID: Option<int> = None
return (...인자들: Array<T>) => {
// 기존 타이머 취소
match 타이머ID {
case Some(id) => clearTimeout(id)
case None => {}
}
// 새 타이머 설정
타이머ID = Some(setTimeout(() => {
함수(...인자들)
타이머ID = None
}, 지연시간))
}
}
// 스로틀 함수
function throttle<T>(
함수: (...T) -> void,
간격: int
) -> (...T) -> void {
let mut 마지막호출시간 = 0
let mut 타이머ID: Option<int> = None
return (...인자들: Array<T>) => {
let 현재시간 = Date.now()
let 경과시간 = 현재시간 - 마지막호출시간
if 경과시간 >= 간격 {
함수(...인자들)
마지막호출시간 = 현재시간
} else if 타이머ID.isNone() {
타이머ID = Some(setTimeout(() => {
함수(...인자들)
마지막호출시간 = Date.now()
타이머ID = None
}, 간격 - 경과시간))
}
}
}
// 사용 예제
let 검색함수 = (쿼리: string) => {
print("검색 실행: {쿼리}")
}
let 디바운스된검색 = debounce(검색함수, 300) // 300ms 후 실행
let 스로틀된검색 = throttle(검색함수, 1000) // 1초마다 최대 1회
🎭 함수형 데이터 변환
렌즈 (Lenses)
// 기본 렌즈 구현
struct Lens<S, A> {
get: (S) -> A,
set: (A, S) -> S
}
impl<S, A> Lens<S, A> {
function new(getter: (S) -> A, setter: (A, S) -> S) -> Lens<S, A> {
return Lens { get: getter, set: setter }
}
function compose<B>(other: Lens<A, B>) -> Lens<S, B> {
return Lens.new(
(s: S) => other.get(self.get(s)),
(b: B, s: S) => {
let a = self.get(s)
let newA = other.set(b, a)
return self.set(newA, s)
}
)
}
function over(함수: (A) -> A, 구조체: S) -> S {
let 현재값 = self.get(구조체)
let 새값 = 함수(현재값)
return self.set(새값, 구조체)
}
}
// 사용자 구조체
struct 사용자 {
이름: string,
나이: int,
주소: 주소
}
struct 주소 {
도시: string,
우편번호: string
}
// 렌즈 정의
let 이름렌즈 = Lens.new(
(user: 사용자) => user.이름,
(이름: string, user: 사용자) => user{ 이름 }
)
let 주소렌즈 = Lens.new(
(user: 사용자) => user.주소,
(주소: 주소, user: 사용자) => user{ 주소 }
)
let 도시렌즈 = Lens.new(
(addr: 주소) => addr.도시,
(도시: string, addr: 주소) => addr{ 도시 }
)
// 렌즈 조합
let 사용자도시렌즈 = 주소렌즈.compose(도시렌즈)
// 사용 예제
let 사용자 = 사용자 {
이름: "김철수",
나이: 30,
주소: 주소 { 도시: "서울", 우편번호: "12345" }
}
let 새사용자 = 사용자도시렌즈.set("부산", 사용자)
let 수정된사용자 = 이름렌즈.over(이름 => 이름.toUpperCase(), 새사용자)
print("원본 도시: {사용자도시렌즈.get(사용자)}") // "서울"
print("새 도시: {사용자도시렌즈.get(새사용자)}") // "부산"
함수형 데이터 파이프라인
// 데이터 변환 파이프라인
struct Pipeline<T> {
데이터: T
}
impl<T> Pipeline<T> {
function from(데이터: T) -> Pipeline<T> {
return Pipeline { 데이터 }
}
function map<U>(변환함수: (T) -> U) -> Pipeline<U> {
return Pipeline.from(변환함수(self.데이터))
}
function filter(조건함수: (T) -> bool) -> Pipeline<Option<T>> {
if 조건함수(self.데이터) {
return Pipeline.from(Some(self.데이터))
} else {
return Pipeline.from(None)
}
}
function tap(부작용함수: (T) -> void) -> Pipeline<T> {
부작용함수(self.데이터)
return self
}
function getValue() -> T {
return self.데이터
}
}
// 데이터 처리 예제
let 결과 = Pipeline.from(" Hello World ")
.map(s => s.trim())
.tap(s => print("트림 후: '{s}'"))
.map(s => s.toLowerCase())
.tap(s => print("소문자 변환: '{s}'"))
.map(s => s.split(" "))
.map(arr => arr.join("-"))
.getValue() // "hello-world"
print("최종 결과: {결과}")
🏗️ 모나드 (Monads)
Maybe/Option 모나드
// Option 모나드를 네임스페이스 함수로 표현
module Option {
function map<T, U>(옵션: Option<T>, 변환: (T) -> U) -> Option<U> {
match 옵션 {
case Some(값) => Some(변환(값))
case None => None
}
}
function flatMap<T, U>(옵션: Option<T>, 함수: (T) -> Option<U>) -> Option<U> {
match 옵션 {
case Some(값) => 함수(값)
case None => None
}
}
function filter<T>(옵션: Option<T>, 조건: (T) -> bool) -> Option<T> {
match 옵션 {
case Some(값) if 조건(값) => Some(값)
case _ => None
}
}
function orElse<T>(옵션: Option<T>, 대안: () -> Option<T>) -> Option<T> {
match 옵션 {
case Some(_) => 옵션
case None => 대안()
}
}
}
// 체이닝 예제
let 사용자데이터 = Some("김철수")
let 처리결과 = 사용자데이터
|> Option.filter(_, 이름 => 이름.length > 2)
|> Option.map(_, 이름 => 이름.toUpperCase())
|> Option.flatMap(_, 이름 => {
if 이름.startsWith("김") {
return Some("성씨: {이름.substr(0, 1)}")
} else {
return None
}
})
|> Option.orElse(_, () => Some("알 수 없는 사용자"))
print("처리 결과: {처리결과}") // Some("성씨: 김")
Result 모나드
// Result 모나드 확장
impl<T, E> Result<T, E> {
function flatMap<U>(함수: (T) -> Result<U, E>) -> Result<U, E> {
match self {
case Ok(값) => 함수(값)
case Err(오류) => Err(오류)
}
}
function mapError<F>(함수: (E) -> F) -> Result<T, F> {
match self {
case Ok(값) => Ok(값)
case Err(오류) => Err(함수(오류))
}
}
function recover(복구함수: (E) -> T) -> T {
match self {
case Ok(값) => 값
case Err(오류) => 복구함수(오류)
}
}
}
// 안전한 연산 체이닝
function safeDivide(분자: float, 분모: float) -> Result<float, string> {
if 분모 == 0.0 {
return Err("0으로 나눌 수 없음")
}
return Ok(분자 / 분모)
}
function safeSquareRoot(값: float) -> Result<float, string> {
if 값 < 0.0 {
return Err("음수의 제곱근 계산 불가")
}
return Ok(Math.sqrt(값))
}
let 계산결과 = Ok(16.0)
.flatMap(값 => safeDivide(값, 4.0)) // 16 / 4 = 4
.flatMap(값 => safeSquareRoot(값)) // √4 = 2
.map(값 => 값 * 2) // 2 * 2 = 4
.recover(오류 => {
print("오류 발생: {오류}")
return 0.0
})
print("계산 결과: {계산결과}") // 4.0
🎨 함수형 패턴
전략 패턴 (Strategy Pattern)
// 함수형 전략 패턴
enum 정렬전략 {
QuickSort,
MergeSort,
BubbleSort
}
function 정렬하기<T>(
배열: Array<T>,
전략: 정렬전략,
비교함수: (T, T) -> int
) -> Array<T> {
let 정렬함수 = match 전략 {
case QuickSort => quickSort
case MergeSort => mergeSort
case BubbleSort => bubbleSort
}
return 정렬함수(배열, 비교함수)
}
function quickSort<T>(배열: Array<T>, 비교: (T, T) -> int) -> Array<T> {
// 퀵정렬 구현
if 배열.length <= 1 { return 배열 }
let 피벗 = 배열[배열.length / 2]
let 작은것들 = 배열.filter(x => 비교(x, 피벗) < 0)
let 같은것들 = 배열.filter(x => 비교(x, 피벗) == 0)
let 큰것들 = 배열.filter(x => 비교(x, 피벗) > 0)
return [
...quickSort(작은것들, 비교),
...같은것들,
...quickSort(큰것들, 비교)
]
}
// 사용 예제
let 숫자배열 = [64, 34, 25, 12, 22, 11, 90]
let 정렬된배열 = 정렬하기(
숫자배열,
QuickSort,
(a, b) => a - b
)
print("정렬된 배열: {정렬된배열}")
관찰자 패턴 (Observer Pattern)
// 함수형 관찰자 패턴
struct Observable<T> {
subscribers: Array<(T) -> void>
}
impl<T> Observable<T> {
function new() -> Observable<T> {
return Observable { subscribers: [] }
}
function subscribe(콜백: (T) -> void) -> () -> void {
self.subscribers.push(콜백)
// 구독 해제 함수 반환
return () => {
self.subscribers = self.subscribers.filter(s => s != 콜백)
}
}
function next(값: T) {
self.subscribers.forEach(콜백 => 콜백(값))
}
function map<U>(변환함수: (T) -> U) -> Observable<U> {
let 새옵저버블 = Observable.new()
self.subscribe(값 => {
새옵저버블.next(변환함수(값))
})
return 새옵저버블
}
function filter(조건함수: (T) -> bool) -> Observable<T> {
let 새옵저버블 = Observable.new()
self.subscribe(값 => {
if 조건함수(값) {
새옵저버블.next(값)
}
})
return 새옵저버블
}
}
// 사용 예제
let 클릭스트림 = Observable.new()
let 구독해제1 = 클릭스트림
.filter(이벤트 => 이벤트.타입 == "click")
.map(이벤트 => 이벤트.좌표)
.subscribe(좌표 => print("클릭 위치: {좌표}"))
let 구독해제2 = 클릭스트림
.filter(이벤트 => 이벤트.더블클릭)
.subscribe(이벤트 => print("더블클릭 감지"))
// 이벤트 발생
클릭스트림.next({ 타입: "click", 좌표: [100, 200], 더블클릭: false })
클릭스트림.next({ 타입: "click", 좌표: [300, 400], 더블클릭: true })
// 나중에 구독 해제
구독해제1()
구독해제2()
🚀 고급 함수형 테크닉
Y 콤비네이터 (Y Combinator)
// Y 콤비네이터로 재귀 함수 구현
function Y<T>(함수: ((T) -> T) -> (T) -> T) -> (T) -> T {
return function(인자: T) -> T {
return 함수(Y(함수))(인자)
}
}
// 팩토리얼을 Y 콤비네이터로 구현
let 팩토리얼 = Y((재귀함수: (int) -> int) => (n: int) => {
if n <= 1 {
return 1
} else {
return n * 재귀함수(n - 1)
}
})
let 팩토리얼5 = 팩토리얼(5) // 120
// 피보나치를 Y 콤비네이터로 구현
let 피보나치Y = Y((재귀함수: (int) -> int) => (n: int) => {
if n <= 1 {
return n
} else {
return 재귀함수(n - 1) + 재귀함수(n - 2)
}
})
let 피보8 = 피보나치Y(8) // 21
print("Y 콤비네이터 팩토리얼(5): {팩토리얼5}")
print("Y 콤비네이터 피보나치(8): {피보8}")
지연 평가 (Lazy Evaluation)
// 지연 평가 구조체
struct Lazy<T> {
계산함수: () -> T,
캐시: Option<T>
}
impl<T> Lazy<T> {
function new(계산함수: () -> T) -> Lazy<T> {
return Lazy { 계산함수, 캐시: None }
}
function get() -> T {
match self.캐시 {
case Some(값) => 값
case None => {
let 계산결과 = self.계산함수()
self.캐시 = Some(계산결과)
return 계산결과
}
}
}
function map<U>(변환함수: (T) -> U) -> Lazy<U> {
return Lazy.new(() => 변환함수(self.get()))
}
}
// 무한 시퀀스
struct LazySequence<T> {
생성함수: (int) -> T
}
impl<T> LazySequence<T> {
function new(생성함수: (int) -> T) -> LazySequence<T> {
return LazySequence { 생성함수 }
}
function take(개수: int) -> Array<T> {
let mut 결과: Array<T> = []
for i in 0..개수 {
결과.push(self.생성함수(i))
}
return 결과
}
function map<U>(변환함수: (T) -> U) -> LazySequence<U> {
return LazySequence.new(인덱스 => 변환함수(self.생성함수(인덱스)))
}
function filter(조건함수: (T) -> bool) -> LazySequence<T> {
return LazySequence.new(인덱스 => {
let mut 현재인덱스 = 인덱스
let mut 찾은개수 = 0
while true {
let 값 = self.생성함수(현재인덱스)
if 조건함수(값) {
if 찾은개수 == 인덱스 {
return 값
}
찾은개수 += 1
}
현재인덱스 += 1
}
})
}
}
// 자연수 무한 시퀀스
let 자연수 = LazySequence.new(i => i + 1)
let 처음10개자연수 = 자연수.take(10) // [1, 2, 3, ..., 10]
// 짝수만 추출
let 짝수시퀀스 = 자연수.filter(x => x % 2 == 0)
let 처음5개짝수 = 짝수시퀀스.take(5) // [2, 4, 6, 8, 10]
// 제곱 시퀀스
let 제곱시퀀스 = 자연수.map(x => x * x)
let 처음5개제곱 = 제곱시퀀스.take(5) // [1, 4, 9, 16, 25]
print("처음 10개 자연수: {처음10개자연수}")
print("처음 5개 짝수: {처음5개짝수}")
print("처음 5개 제곱: {처음5개제곱}")
토파즈의 함수형 라이브러리는 선언적이고 표현력 있는 프로그래밍을 가능하게 합니다. 고차함수, 모나드, 지연평가 등을 활용하여 더 우아하고 안전한 코드를 작성하세요! ✨