함수와 클로저

토파즈의 강력한 함수 시스템을 마스터하세요. 함수 선언, 화살표 함수, 고차 함수, 클로저, 그리고 함수형 프로그래밍 패턴을 심층적으로 학습합니다.

함수는 토파즈에서 1등 시민입니다. 변수에 저장하고, 다른 함수의 매개변수로 전달하고, 함수에서 반환할 수 있습니다. 🚀

🔧 기본 함수 선언

표준 함수 선언

// 기본 함수 구조
function 인사하기(이름: string) -> string {
    return "안녕하세요, {이름}님!"
}

// 매개변수 없는 함수
function 현재시간() -> string {
    return "2024년 12월 20일"
}

// 반환값 없는 함수 (void)
function 로그출력(메시지: string) {
    print("로그: {메시지}")
}

// 사용 예시
let 인사말 = 인사하기("토파즈 개발자")
print(인사말)  // "안녕하세요, 토파즈 개발자님!"

매개변수와 기본값

// 기본 매개변수값
function 계산하기(a: int, b: int = 10, 연산: string = "더하기") -> int {
    match 연산 {
        case "더하기" => return a + b
        case "빼기" => return a - b
        case "곱하기" => return a * b
        case _ => return 0
    }
}

// 다양한 호출 방식
let 결과1 = 계산하기(5)           // 5 + 10 = 15
let 결과2 = 계산하기(5, 3)        // 5 + 3 = 8
let 결과3 = 계산하기(5, 3, "곱하기")  // 5 * 3 = 15

// 명명된 매개변수
let 결과4 = 계산하기(a: 8, 연산: "빼기", b: 3)  // 8 - 3 = 5

가변 매개변수

// 가변 매개변수 (...rest)
function 합계구하기(숫자들: ...int) -> int {
    let mut 합계 = 0
    for 숫자 in 숫자들 {
        합계 += 숫자
    }
    return 합계
}

let 총합1 = 합계구하기(1, 2, 3, 4, 5)        // 15
let 총합2 = 합계구하기(10, 20)               // 30

// 배열과 추가 매개변수 결합
function 통계계산(레이블: string, 값들: ...float) -> string {
    let 개수 = 값들.length()
    let 평균 = 값들.sum() / 개수
    return "{레이블}: 개수={개수}, 평균={평균}"
}

print(통계계산("시험점수", 85.5, 92.0, 78.5, 89.0))

➡️ 화살표 함수

기본 화살표 함수

// 간단한 화살표 함수
let 제곱 = (x: int) => x * x
let 인사 = (이름: string) => "안녕, {이름}!"

// 매개변수 없는 화살표 함수
let 랜덤숫자 = () => Math.random() * 100

// 복잡한 화살표 함수
let 사용자정보 = (이름: string, 나이: int) => {
    let 상태 = if 나이 >= 18 { "성인" } else { "미성년자" }
    return {
        이름: 이름,
        나이: 나이,
        상태: 상태
    }
}

// 사용 예시
print(제곱(5))                    // 25
print(인사("김토파즈"))            // "안녕, 김토파즈!"
let 정보 = 사용자정보("이개발", 25)
print(정보)                      // { 이름: "이개발", 나이: 25, 상태: "성인" }

배열 메서드와 화살표 함수

let 숫자들 = [1, 2, 3, 4, 5]

// map 변환
let 제곱들 = 숫자들.map(x => x * x)
print(제곱들)  // [1, 4, 9, 16, 25]

// filter 필터링
let 짝수들 = 숫자들.filter(x => x % 2 == 0)
print(짝수들)  // [2, 4]

// reduce 축약
let 합계 = 숫자들.reduce((누적, 현재) => 누적 + 현재, 0)
print(합계)   // 15

// 복잡한 체이닝
let 결과 = 숫자들
    .filter(x => x > 2)
    .map(x => x * 3)
    .reduce((a, b) => a + b, 0)
print(결과)   // (3 + 4 + 5) * 3 = 36

🎯 고차 함수

함수를 매개변수로 받기

// 고차 함수 정의
function 연산적용(배열: [int], 연산함수: function(int) -> int) -> [int] {
    let 결과 = []
    for 요소 in 배열 {
        결과.push(연산함수(요소))
    }
    return 결과
}

// 함수를 매개변수로 전달
let 숫자들 = [1, 2, 3, 4]
let 제곱결과 = 연산적용(숫자들, x => x * x)
let 두배결과 = 연산적용(숫자들, x => x * 2)

print(제곱결과)  // [1, 4, 9, 16]
print(두배결과)  // [2, 4, 6, 8]

// 조건부 처리 함수
function 조건부처리(값: int, 조건: function(int) -> bool, 처리함수: function(int) -> int) -> int {
    if 조건(값) {
        return 처리함수(값)
    }
    return
}

let 결과 = 조건부처리(15, x => x > 10, x => x * 2)
print(결과)  // 30 (15 > 10이므로 15 * 2)

함수를 반환하기

// 함수 팩토리
function 곱셈기만들기(배수: int) -> function(int) -> int {
    return function(x: int) -> int {
        return x * 배수
    }
}

let 두배만들기 = 곱셈기만들기(2)
let 세배만들기 = 곱셈기만들기(3)

print(두배만들기(5))  // 10
print(세배만들기(4))  // 12

// 설정 가능한 검증 함수
function 범위검사기만들기(최소: int, 최대: int) -> function(int) -> bool {
    return function(값: int) -> bool {
        return>= 최소 &&<= 최대
    }
}

let 성인나이검사 = 범위검사기만들기(18, 65)
let 학생나이검사 = 범위검사기만들기(5, 18)

print(성인나이검사(25))  // true
print(학생나이검사(16))  // true
print(학생나이검사(25))  // false

�� 클로저

기본 클로저

function 카운터만들기(초기값: int) -> function() -> int {
    let mut 카운트 = 초기값  // 클로저에 의해 캡처됨
    
    return function() -> int {
        카운트 += 1      // 외부 변수에 접근
        return 카운트
    }
}

let 카운터1 = 카운터만들기(0)
let 카운터2 = 카운터만들기(100)

print(카운터1())  // 1
print(카운터1())  // 2
print(카운터2())  // 101
print(카운터1())  // 3

복잡한 클로저 패턴

// 상태를 가진 클로저
function 계좌만들기(초기잔액: float) -> { 입금: function(float) -> float, 출금: function(float) -> float, 잔액확인: function() -> float } {
    let mut 잔액 = 초기잔액
    
    return {
        입금: function(금액: float) -> float {
            잔액 += 금액
            return 잔액
        },
        출금: function(금액: float) -> float {
            if 금액 <= 잔액 {
                잔액 -= 금액
            }
            return 잔액
        },
        잔액확인: function() -> float {
            return 잔액
        }
    }
}

let 내계좌 = 계좌만들기(1000.0)
print(내계좌.입금(500.0))    // 1500.0
print(내계좌.출금(200.0))    // 1300.0
print(내계좌.잔액확인())      // 1300.0

// 환경 캡처하는 클로저
function 설정관리자(기본설정: { [string]: any }) -> function(string, any) -> any {
    let mut 현재설정 = 기본설정.copy()
    
    return function(키: string, 값: any = null) -> any {
        if!= null {
            현재설정[키] =
        }
        return 현재설정[키]
    }
}

let 앱설정 = 설정관리자({
    테마: "다크",
    언어: "한국어",
    알림: true
})

print(앱설정("테마"))          // "다크"
앱설정("테마", "라이트")
print(앱설정("테마"))          // "라이트"

🔄 재귀 함수

기본 재귀

// 팩토리얼 계산
function 팩토리얼(n: int) -> int {
    if n <= 1 {
        return 1
    }
    return n * 팩토리얼(n - 1)
}

print(팩토리얼(5))  // 120

// 피보나치 수열
function 피보나치(n: int) -> int {
    if n <= 1 {
        return n
    }
    return 피보나치(n - 1) + 피보나치(n - 2)
}

print(피보나치(8))  // 21

// 배열 합계 (재귀적)
function 배열합계(배열: [int]) -> int {
    if 배열.length() == 0 {
        return 0
    }
    if 배열.length() == 1 {
        return 배열[0]
    }
    return 배열[0] + 배열합계(배열.slice(1))
}

print(배열합계([1, 2, 3, 4, 5]))  // 15

꼬리 재귀 최적화

// 꼬리 재귀 팩토리얼
function 꼬리팩토리얼(n: int, 누적: int = 1) -> int {
    if n <= 1 {
        return 누적
    }
    return 꼬리팩토리얼(n - 1, 누적 * n)  // 꼬리 호출
}

// 꼬리 재귀 피보나치
function 꼬리피보나치(n: int, a: int = 0, b: int = 1) -> int {
    if n == 0 {
        return a
    }
    return 꼬리피보나치(n - 1, b, a + b)  // 꼬리 호출
}

print(꼬리팩토리얼(10))  // 3628800
print(꼬리피보나치(10))  // 55

⚡ 함수형 프로그래밍 패턴

커링 (Currying)

// 커링된 함수
function 더하기(a: int) -> function(int) -> int {
    return function(b: int) -> int {
        return a + b
    }
}

let 5더하기 = 더하기(5)
print(5더하기(3))  // 8

// 다중 매개변수 커링
function 곱하고더하기(곱수: float) -> function(float) -> function(float) -> float {
    return function(더할수: float) -> function(float) -> float {
        return function(값: float) -> float {
            return* 곱수 + 더할수
        }
    }
}

let 두배하고십더하기 = 곱하고더하기(2.0)(10.0)
print(두배하고십더하기(5.0))  // 20.0 (5 * 2 + 10)

함수 합성

// 함수 합성 유틸리티
function 합성하기<T, U, V>(f: function(U) -> V, g: function(T) -> U) -> function(T) -> V {
    return function(x: T) -> V {
        return f(g(x))
    }
}

let 제곱 = (x: int) => x * x
let 두배 = (x: int) => x * 2
let 문자열로 = (x: int) => x.toString()

// 함수들을 합성
let 제곱후두배 = 합성하기(두배, 제곱)
let 제곱후두배후문자열 = 합성하기(문자열로, 제곱후두배)

print(제곱후두배(3))         // 18 (3² * 2)
print(제곱후두배후문자열(4))  // "32" (4² * 2 → 문자열)

메모이제이션

// 메모이제이션 데코레이터
function 메모이제이션<T, R>(함수: function(T) -> R) -> function(T) -> R {
    let mut 캐시 = {}
    
    return function(입력: T) -> R {
        let= 입력.toString()
        ifin 캐시.keys {
            return 캐시[키]
        }
        
        let 결과 = 함수(입력)
        캐시[키] = 결과
        return 결과
    }
}

// 느린 피보나치를 메모이제이션으로 최적화
let 메모피보나치 = 메모이제이션(function(n: int) -> int {
    if n <= 1 return n
    return 메모피보나치(n - 1) + 메모피보나치(n - 2)
})

// 이제 큰 숫자도 빠르게 계산
print(메모피보나치(40))  // 빠르게 계산됨

🛡️ 함수 최적화와 베스트 프랙티스

1. 순수 함수 사용

// 좋은 예: 순수 함수
function 세금계산(소득: float, 세율: float) -> float {
    return 소득 * 세율
}

// 피해야 할: 부작용이 있는 함수
let mut 전역카운터 = 0
function 나쁜함수(값: int) -> int {
    전역카운터 += 1  // 부작용!
    return* 2
}

// 좋은 대안: 상태 반환
function 좋은함수(값: int, 현재카운터: int) -> { 결과: int, 새카운터: int } {
    return {
        결과:* 2,
        새카운터: 현재카운터 + 1
    }
}

2. 함수 이름과 구조화

// 명확한 함수 이름
function 사용자이메일유효성검사(이메일: string) -> bool {
    return 이메일.contains("@") && 이메일.contains(".")
}

function 주문총액계산(주문항목들: [{ 가격: float, 수량: int }]) -> float {
    return 주문항목들
        .map(항목 => 항목.가격 * 항목.수량)
        .reduce((합계, 금액) => 합계 + 금액, 0.0)
}

// 단일 책임 원칙
function 사용자데이터파싱(원시데이터: string) -> { 이름: string, 나이: int } {
    // 파싱만 담당
    let 부분들 = 원시데이터.split(",")
    return {
        이름: 부분들[0].trim(),
        나이: parseInt(부분들[1].trim())
    }
}

function 사용자데이터검증(사용자: { 이름: string, 나이: int }) -> bool {
    // 검증만 담당
    return 사용자.이름.length() > 0 && 사용자.나이 > 0
}

3. 에러 처리

// Result 타입을 사용한 안전한 함수
function 안전한나누기(분자: float, 분모: float) -> Result<float, string> {
    if 분모 == 0.0 {
        return Err("0으로 나눌 수 없습니다")
    }
    return Ok(분자 / 분모)
}

// 사용 시
match 안전한나누기(10.0, 2.0) {
    case Ok(결과) => print("결과: {결과}")
    case Err(오류) => print("오류: {오류}")
}

함수와 클로저는 토파즈의 핵심 기능입니다. 이들을 효과적으로 활용하면 재사용 가능하고 테스트 가능한 코드를 작성할 수 있습니다! 🎯