Topaz

함수형 라이브러리

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

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

고차 함수 (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)

// 명시적 helper 함수로 구성한 기본 렌즈 구현
struct Lens<S, A> {
    get: (S) -> A,
    set: (A, S) -> S
}

function 렌즈생성<S, A>(getter: (S) -> A, setter: (A, S) -> S) -> Lens<S, A> {
    return Lens { get: getter, set: setter }
}

function 렌즈조합<S, A, B>(바깥렌즈: Lens<S, A>, 안쪽렌즈: Lens<A, B>) -> Lens<S, B> {
    return 렌즈생성(
        (값: S) => 안쪽렌즈.get(바깥렌즈.get(값)),
        (대체값: B, 값: S) => {
            let 현재초점 = 바깥렌즈.get(값)
            let 새초점 = 안쪽렌즈.set(대체값, 현재초점)
            return 바깥렌즈.set(새초점, 값)
        }
    )
}

function 렌즈변환<S, A>(렌즈: Lens<S, A>, 변환함수: (A) -> A, 값: S) -> S {
    let 현재값 = 렌즈.get(값)
    return 렌즈.set(변환함수(현재값), 값)
}

// 사용자 구조체
struct 사용자 {
    이름: string,
    나이: int,
    주소: 주소
}

struct 주소 {
    도시: string,
    우편번호: string
}

// 렌즈 정의
let 이름렌즈 = 렌즈생성(
    (user: 사용자) => user.이름,
    (이름: string, user: 사용자) => user{ 이름 }
)

let 주소렌즈 = 렌즈생성(
    (user: 사용자) => user.주소,
    (주소: 주소, user: 사용자) => user{ 주소 }
)

let 도시렌즈 = 렌즈생성(
    (addr: 주소) => addr.도시,
    (도시: string, addr: 주소) => addr{ 도시 }
)

// 렌즈 조합
let 사용자도시렌즈 = 렌즈조합(주소렌즈, 도시렌즈)

// 사용 예제
let 사용자 = 사용자 {
    이름: "minji",
    나이: 30,
    주소: 주소 { 도시: "서울", 우편번호: "12345" }
}

let 새사용자 = 사용자도시렌즈.set("부산", 사용자)
let 수정된사용자 = 렌즈변환(이름렌즈, 이름 => 이름.toUpperCase(), 새사용자)

print("원본 도시: {사용자도시렌즈.get(사용자)}")      // "서울"
print("새 도시: {사용자도시렌즈.get(새사용자)}")       // "부산"
print("수정된 이름: {수정된사용자.이름}")              // "MINJI"

함수형 데이터 파이프라인

// 데이터 변환 파이프라인
struct Pipeline<T> {
    데이터: T
}

function 파이프라인시작<T>(데이터: T) -> Pipeline<T> {
    return Pipeline { 데이터 }
}

function 파이프라인맵<T, U>(파이프라인: Pipeline<T>, 변환함수: (T) -> U) -> Pipeline<U> {
    return Pipeline { 데이터: 변환함수(파이프라인.데이터) }
}

function 파이프라인필터<T>(파이프라인: Pipeline<T>, 조건함수: (T) -> bool) -> Pipeline<Option<T>> {
    if 조건함수(파이프라인.데이터) {
        return Pipeline { 데이터: Some(파이프라인.데이터) }
    } else {
        return Pipeline { 데이터: None }
    }
}

function 파이프라인탭<T>(파이프라인: Pipeline<T>, 부작용함수: (T) -> void) -> Pipeline<T> {
    부작용함수(파이프라인.데이터)
    return 파이프라인
}

function 파이프라인값<T>(파이프라인: Pipeline<T>) -> T {
    return 파이프라인.데이터
}

// 데이터 처리 예제
let 결과 = 파이프라인시작("  Hello World  ")
    |> 파이프라인맵(_, s => s.trim())
    |> 파이프라인탭(_, s => print("트림 후: '{s}'"))
    |> 파이프라인맵(_, s => s.toLowerCase())
    |> 파이프라인탭(_, s => print("소문자 변환: '{s}'"))
    |> 파이프라인맵(_, s => s.split(" "))
    |> 파이프라인맵(_, arr => arr.join("-"))
    |> 파이프라인값(_)                             // "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 helper 함수를 네임스페이스로 구성
module Result도구 {
    function map<T, U, E>(결과: Result<T, E>, 변환함수: (T) -> U) -> Result<U, E> {
        match 결과 {
            case Ok(값) => Ok(변환함수(값))
            case Err(오류) => Err(오류)
        }
    }

    function flatMap<T, U, E>(결과: Result<T, E>, 함수: (T) -> Result<U, E>) -> Result<U, E> {
        match 결과 {
            case Ok(값) => 함수(값)
            case Err(오류) => Err(오류)
        }
    }

    function mapError<T, E, F>(결과: Result<T, E>, 함수: (E) -> F) -> Result<T, F> {
        match 결과 {
            case Ok(값) => Ok(값)
            case Err(오류) => Err(함수(오류))
        }
    }

    function recover<T, E>(결과: Result<T, E>, 복구함수: (E) -> T) -> T {
        match 결과 {
            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)
    |> Result도구.flatMap(_, 값 => safeDivide(값, 4.0))        // 16 / 4 = 4
    |> Result도구.flatMap(_, 값 => safeSquareRoot(값))          // √4 = 2
    |> Result도구.map(_, 값 =>* 2)                          // 2 * 2 = 4
    |> Result도구.mapError(_, 오류 => "계산 실패: {오류}")
    |> Result도구.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>
}

function 옵저버블생성<T>() -> Observable<T> {
    return Observable { subscribers: [] }
}

function 옵저버블구독<T>(옵저버블: Observable<T>, 콜백: (T) -> void) -> Observable<T> {
    return 옵저버블{ subscribers: [...옵저버블.subscribers, 콜백] }
}

function 옵저버블발행<T>(옵저버블: Observable<T>, 값: T) {
    옵저버블.subscribers.forEach(콜백 => 콜백(값))
}

function 옵저버변환<T, U>(변환함수: (T) -> U, 콜백: (U) -> void) -> (T) -> void {
    return=> 콜백(변환함수(값))
}

function 옵저버필터<T>(조건함수: (T) -> bool, 콜백: (T) -> void) -> (T) -> void {
    return=> {
        if 조건함수(값) {
            콜백(값)
        }
    }
}

// 사용 예제
let 클릭위치옵저버 = 옵저버필터(
    이벤트 => 이벤트.타입 == "click",
    옵저버변환(
        이벤트 => 이벤트.좌표,
        좌표 => print("클릭 위치: {좌표}")
    )
)

let 더블클릭옵저버 = 옵저버필터(
    이벤트 => 이벤트.더블클릭,
    이벤트 => print("더블클릭 감지")
)

let 클릭스트림0 = 옵저버블생성()
let 클릭스트림1 = 옵저버블구독(클릭스트림0, 클릭위치옵저버)
let 클릭스트림2 = 옵저버블구독(클릭스트림1, 더블클릭옵저버)

// 이벤트 발생
옵저버블발행(클릭스트림2, { 타입: "click", 좌표: [100, 200], 더블클릭: false })
옵저버블발행(클릭스트림2, { 타입: "click", 좌표: [300, 400], 더블클릭: true })

고급 함수형 테크닉

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>
}

function 지연값생성<T>(계산함수: () -> T) -> Lazy<T> {
    return Lazy { 계산함수, 캐시: None }
}

function 지연값계산<T>(지연값: Lazy<T>) -> { lazy: Lazy<T>, value: T } {
    match 지연값.캐시 {
        case Some(값) => return { lazy: 지연값, value: 값 }
        case None => {
            let 계산결과 = 지연값.계산함수()
            let 캐시된지연값 = 지연값{ 캐시: Some(계산결과) }
            return { lazy: 캐시된지연값, value: 계산결과 }
        }
    }
}

function 지연값변환<T, U>(지연값: Lazy<T>, 변환함수: (T) -> U) -> Lazy<U> {
    return 지연값생성(() => {
        let 계산된값 = 지연값계산(지연값)
        return 변환함수(계산된값.value)
    })
}

// 무한 시퀀스
struct LazySequence<T> {
    생성함수: (int) -> T
}

function 지연시퀀스생성<T>(생성함수: (int) -> T) -> LazySequence<T> {
    return LazySequence { 생성함수 }
}

function 지연시퀀스가져오기<T>(시퀀스: LazySequence<T>, 개수: int) -> Array<T> {
    let mut 결과: Array<T> = []
    for i in 0..<개수 {
        결과.push(시퀀스.생성함수(i))
    }
    return 결과
}

function 지연시퀀스변환<T, U>(시퀀스: LazySequence<T>, 변환함수: (T) -> U) -> LazySequence<U> {
    return 지연시퀀스생성(인덱스 => 변환함수(시퀀스.생성함수(인덱스)))
}

function 지연시퀀스필터<T>(시퀀스: LazySequence<T>, 조건함수: (T) -> bool) -> LazySequence<T> {
    return 지연시퀀스생성(인덱스 => {
        let mut 현재인덱스 = 0
        let mut 찾은개수 = 0

        while true {
            let= 시퀀스.생성함수(현재인덱스)
            if 조건함수(값) {
                if 찾은개수 == 인덱스 {
                    return
                }
                찾은개수 += 1
            }
            현재인덱스 += 1
        }
    })
}

// 캐시되는 지연 값
let 비싼값0 = 지연값생성(() => "topaz".toUpperCase())
let 첫계산 = 지연값계산(비싼값0)
let 비싼값1 = 첫계산.lazy
let 두번째계산 = 지연값계산(비싼값1)
let 길이지연값 = 지연값변환(비싼값1, 값 =>.length)
let 길이계산 = 지연값계산(길이지연값)

print("첫 지연 값: {첫계산.value}")                  // "TOPAZ"
print("캐시된 지연 값: {두번째계산.value}")          // "TOPAZ"
print("변환된 지연 값: {길이계산.value}")            // 5

// 자연수 무한 시퀀스
let 자연수 = 지연시퀀스생성(i => i + 1)
let 처음10개자연수 = 지연시퀀스가져오기(자연수, 10)    // [1, 2, 3, ..., 10]

// 짝수만 추출
let 짝수시퀀스 = 지연시퀀스필터(자연수, x => x % 2 == 0)
let 처음5개짝수 = 지연시퀀스가져오기(짝수시퀀스, 5)     // [2, 4, 6, 8, 10]

// 제곱 시퀀스
let 제곱시퀀스 = 지연시퀀스변환(자연수, x => x * x)
let 처음5개제곱 = 지연시퀀스가져오기(제곱시퀀스, 5)     // [1, 4, 9, 16, 25]

print("처음 10개 자연수: {처음10개자연수}")
print("처음 5개 짝수: {처음5개짝수}")
print("처음 5개 제곱: {처음5개제곱}")

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