함수형 라이브러리

토파즈의 함수형 프로그래밍 라이브러리를 완전히 활용하세요. 고차함수, 커링, 합성, 모나드 등 모든 함수형 도구들을 마스터하세요.

토파즈는 강력한 함수형 프로그래밍 라이브러리를 제공하여 선언적이고 표현력 있는 코드 작성을 지원합니다. 모든 함수형 도구들을 활용해 보세요! 🚀

🎯 고차 함수 (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개제곱}")

토파즈의 함수형 라이브러리는 선언적이고 표현력 있는 프로그래밍을 가능하게 합니다. 고차함수, 모나드, 지연평가 등을 활용하여 더 우아하고 안전한 코드를 작성하세요! ✨