Menu

Zero 함수: fun, pub fun, 매개변수, 반환 타입

Zero에서 함수가 어떻게 동작하는지 정리했습니다. fun 키워드, 타입이 붙은 매개변수, 반환 타입, pub 가시성 한정자, 그리고 함수 시그니처에서 raises가 하는 역할까지 살펴봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 — 편집하고 실행하면 결과를 바로 볼 수 있습니다.

함수의 구조

Zero 함수의 일반적인 모양:

fun name(param1: Type1, param2: Type2) -> ReturnType {
    // 본문
    return value
}

각 부분:

  • fun — 함수를 도입하는 키워드.
  • name — 함수 이름.
  • (param1: Type1, ...) — 매개변수 목록. 모든 매개변수는 명시적인 타입을 가집니다.
  • -> ReturnType — 반환 타입.
  • { ... } — 본문. 문장 블록입니다.
  • return valuevalue를 가지고 함수를 종료합니다.

작은 구체적인 예시:

fun double(value: i32) -> i32 {
    return value * 2
}

이게 함수의 전부입니다. i32 하나를 받아 다른 i32를 반환하고, I/O는 하지 않습니다. 본문 안에서 valuelet 스타일 바인딩이고, 다른 지역 변수처럼 사용할 수 있습니다.

함수 호출하기

호출은 예상하는 그대로입니다.

let result = double(21)

인자는 매개변수 타입과 맞아야 합니다. 결과는 result에 바인딩되고, doublei32를 반환하기 때문에 컴파일러는 그 타입을 i32로 추론합니다.

도우미 함수와 main을 묶는 예제 — Run을 눌러 동작을 확인해 보세요.

표준 출력에 math works\n이 찍힙니다.

pub과 가시성

기본적으로 파일에 선언된 함수는 그 파일(또는 모듈 — 프로젝트가 커질수록 가시성 규칙도 빡빡해집니다)에 _비공개_입니다. 모듈 바깥으로 함수를 노출하려면 앞에 pub을 붙입니다.

pub fun greet() -> String {
    return "hello\n"
}

pub이 없으면 다른 모듈의 코드는 greet를 호출할 수 없습니다. 런타임은 사용자 정의 모듈 바깥에서 main을 호출해야 하기 때문에 main은 늘 pub이어야 하죠.

기본을 비공개로 두는 규칙은 따를 만한 가치가 있습니다. 인터페이스로 의도한 것만 표시하고, 나머지는 모듈 안쪽에 그대로 두세요.

반환 타입

모든 함수는 -> 뒤에 반환 타입을 선언합니다. 자주 보게 될 반환 타입:

fun answer() -> i32 { return 42 }
fun ok()     -> bool { return true }
fun label()  -> String { return "ready\n" }
fun nothing() -> Void { }

Void는 값을 만들기보다는 부수 효과를 통해 일을 하는 함수의 반환 타입입니다. Void 함수는 명시적인 return이 필요 없습니다. 본문 끝에 도달하기만 하면 됩니다.

fun log(world: World, message: String) -> Void raises {
    check world.out.write(message)
}

값을 버리는 함수 호출

함수가 값을 반환하는데 그 값에 관심이 없다 해도, 반환을 인정해 줘야 합니다. 관용적으로는 let으로 바인딩합니다.

ignored는 함수의 나머지가 절대 읽지 않는 바인딩입니다. ignored(또는 _)라는 이름은 그 폐기가 의도적임을 알려주는 관습이에요. 반환 값을 조용히 버리는 것보다 약간의 마찰이 있는데, 그게 포인트입니다. 에이전트가 코드를 읽고 생성하는 언어에서, 읽히지 않는 값은 종종 드러내야 할 버그이기 때문이에요.

raises의 역할

실패할 수 있는 함수는 시그니처에 그 사실을 선언합니다. main에서 이미 봤죠.

pub fun main(world: World) -> Void raises {
    check world.out.write("hello\n")
}

raises 절은 단독(어떤 에러든 허용)이거나 구체적일 수 있습니다.

fun validate(ok: Bool) -> i32 raises { InvalidInput } {
    if ok == false {
        raise InvalidInput
    }
    return 42
}

raises { InvalidInput }은 "이 함수는 InvalidInput으로만 실패할 수 있다"는 뜻입니다. 호출자는 check(또는 더 정교한 처리 형태)를 사용해서 에러를 전파하거나 처리해야 합니다.

Raises와 Check에서 다중 에러 타입에서 일어나는 일이나 check가 호출자의 raises 절과 어떻게 상호작용하는지까지 깊이 다룹니다.

제네릭 함수

함수가 둘 이상의 타입에 대해 동작하기를 원한다면, 꺾쇠괄호 안에 타입 매개변수를 선언합니다.

fun makePair<T, U>(left: T, right: U) -> Pair<T, U> {
    return Pair { left: left, right: right }
}

TU는 타입 매개변수입니다. 호출자가 무엇으로 할지 결정하죠. makePair(40, 2_u8)을 호출하면 Pair<i32, u8>이 나옵니다. 제네릭 shape와 제약 조건까지 포함해서는 제네릭 문서에서 다룹니다.

함수가 사는 곳

작은 프로그램이라면 함수를 그냥 .0 파일에 바로 작성합니다. 패키지에서는 함수를 src/ 아래 여러 파일에 흩어 두고, 컴파일러가 파일 간 참조를 해결해 줍니다. 함수가 물리적으로 어디 있든 기본은 같습니다 — fun, 매개변수, 반환 타입, 본문.

스타일 노트

공식 예제에서 볼 수 있는 몇 가지 관행:

  • 함수 이름은 소문자로, 단어를 붙여 쓰거나(makePair) camelCase로. 표준 라이브러리는 camelCase 쪽으로 기웁니다.
  • 함수당 반환 값 하나. 여러 가지를 반환해야 한다면 작은 shape로 만드세요. 튜플의 페어의 튜플 같은 것보다 훨씬 명확합니다.
  • Void 함수는 check 호출만 합니다. 값을 계산하는 함수는 가능하면 I/O를 피합니다. 이 분리는 일부는 문화이고, 일부는 강제됩니다 — 순수한 계산 함수는 world를 받지 않으므로 말 그대로 I/O를 할 수 없거든요.

마지막 포인트는 곱씹어 볼 만합니다. I/O는 World 능력 뒤에 있고, World는 명시적으로 전달되기 때문에 함수의 시그니처가 그 함수가 I/O를 할 가능성이 있는지 알려줍니다. 시그니처에 World가 언급되지 않은 함수는 바깥 세상에 대해 순수합니다. 본문을 읽지 않아도 에이전트(와 사람)가 의지할 수 있는 속성이에요.

다음 글: If/Else

지금까지 if가 지나가듯 등장했죠. 다음 문서는 if/else 표현식을 자세히 다룹니다. 바인딩과 어떻게 상호작용하는지, 그리고 의도적으로 빠진 것(참/거짓 강제 변환 없음, 삼항 연산자 없음)까지 살펴봅니다.

자주 묻는 질문

Zero에서 함수는 어떻게 선언하나요?

fun을 사용합니다: fun 이름(매개변수: 타입) -> 반환타입 { 본문 }. 앞에 pub을 붙이면 모듈 바깥에서도 보이게 만들고, 반환 타입 뒤에 raises를 붙이면 함수가 실패할 수 있음을 선언합니다. 예: pub fun double(value: i32) -> i32 { return value * 2 }.

pub 키워드는 무엇을 하나요?

pub은 선언을 공개로 — 현재 모듈 바깥의 코드에서도 볼 수 있게 — 표시합니다. pub이 없으면 함수는 선언된 파일(또는 패키지)에 비공개입니다. 관습적인 진입점 pub fun main은 런타임이 찾아서 호출할 수 있도록 공개여야 합니다.

Zero에서 함수의 값은 어떻게 반환하나요?

함수 본문 안에서 return 값이라고 적습니다. 표현식은 선언된 반환 타입과 맞아야 해요. 반환 타입이 Void인 함수는 아무것도 반환하지 않고, 명시적인 return 문도 필요 없습니다. 본문 끝에 도달하기만 해도 됩니다.

Zero 함수에 매개변수를 여러 개 둘 수 있나요?

네. 괄호 안에 쉼표로 구분해서 나열하면 되고, 각각에 이름과 타입을 적습니다: fun add(a: i32, b: i32) -> i32 { return a + b }. 각 매개변수는 함수 본문 안에서 let 스타일 바인딩처럼 동작합니다. Zero는 매개변수에 명시적인 타입을 요구합니다. 함수 선언에서 매개변수 타입 추론은 없어요.

함수 시그니처에서 raises는 무슨 뜻인가요?

raises는 함수가 실패할 수 있음을 선언합니다. 단독 raises는 어떤 에러 타입이든 허용하고, raises { InvalidInput }처럼 적으면 특정 명명된 에러로만 제한합니다. 호출자는 check(또는 다른 명시적 처리 형태)를 통해 실패 가능성을 인지해야 하며, 조용히 무시할 수 없습니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기