프로그램 구조

모듈과 가시성

이름 붙은 토파즈 파일을 import와 export로 잇고, 루트를 고르고, 가시성 오류와 순환 import를 고치는 방법입니다.

한 파일이 분명한 책임을 맡고 다른 파일이 그 기능을 써야 할 때 모듈을 사용하세요. .tpz 파일 하나가 곧 모듈 하나입니다. 별도의 module 선언은 없습니다.

파일 두 개로 프로그램 하나 만들기

학습 계획 애플리케이션의 소스 구조는 다음과 같습니다.

study-plan/
├── topaz.toml
├── topaz.lock
└── src/
    ├── main.tpz
    └── plan.tpz

src/plan.tpz는 데이터 모델과 재사용할 연산을 담당합니다.

TOPAZ
export record StudyTask {
    title: string,
    minutes: int,
    done: bool = false,
}

function parseMinutes(text: string) -> Result<int, string> {
    match toInt(text) {
        case Some(value) if value > 0 => Ok(value)
        case _ => Err("minutes must be a positive integer")
    }
}

export function makeTask(
    title: string,
    minuteText: string,
    done: bool = false,
) -> Result<StudyTask, string> {
    let minutes = parseMinutes(minuteText)?
    Ok(StudyTask { title: title, minutes: minutes, done: done })
}

export function summarize(tasks: Array<StudyTask>) -> string {
    let mut remaining = 0
    for task in tasks {
        if !task.done {
            remaining = remaining + task.minutes
        }
    }
    "tasks={tasks.length}, remaining={remaining} minutes"
}

src/main.tpz는 필요한 이름 세 개만 선택해서 가져옵니다.

TOPAZ
import src.plan { StudyTask, makeTask, summarize }

export function main(args: Array<string>, stdin: string) -> Result<int, string> {
    let tasks: Array<StudyTask> = [
        makeTask("Run first program", "10", true)?,
        makeTask("Build application", "25", false)?,
    ]
    print(summarize(tasks))
    Ok(0)
}

패키지 루트를 기준으로 찾아낸 컴파일 단위를 검사하고 실행합니다.

BASH
topaz check --root . --locked
topaz run --root . --locked
출력
tasks=2, remaining=25 minutes

명시한 루트를 기준으로 src.plansrc/plan.tpz를 가리킵니다. 컴파일 단위는 진입 모듈과 import를 따라 닿을 수 있는 모든 모듈로 이루어집니다. 진입점에서 닿지 않는 파일은 이 단위에 들지 않아 검사하지 않습니다.

다른 모듈에 필요한 경계만 공개하기

선언 앞에 export가 없는 이름은 비공개입니다. StudyTask, makeTask, summarize는 모듈 경계를 넘어갑니다. parseMinutes는 구현 세부 사항으로 남습니다. src/main.tpz에서 이 이름을 선택하거나 접근하면 정적 오류가 납니다. 가져온 바인딩은 읽기 전용입니다.

선택 import는 공개 이름을 직접 바인딩합니다.

목적정본 형식
필요한 이름 몇 개 선택import src.plan { StudyTask, makeTask }
선택한 이름 하나 변경import src.plan { summarize as renderSummary }
이름공간 바인딩import src.plan as plan

이름공간 형식에서는 공개 값을 plan.makeTask(...)처럼 씁니다. 이름공간은 컴파일 시점에 이름을 찾는 수단입니다. 런타임 값이 아닙니다. plan.StudyTask 같은 공개 타입은 타입 위치에 쓸 수 있습니다. 다만 plan.StudyTask { ... }는 값을 만드는 형식이 아닙니다. 명목 레코드를 직접 만들려면 StudyTask를 선택해서 가져오세요.

흔한 실수 바로잡기. import src.plan as plan { summarize }처럼 두 import 형식을 합칠 수는 없습니다. 이름공간 import와 선택 import 중에서 하나를 고르세요.

루트, 초기화, 패키지는 서로 다른 결정입니다

  • 모듈 경로는 선택한 루트 아래를 점으로 구분해 가리키는 주소입니다. 파일과 디렉터리 이름이 정확히 일치해야 합니다.
  • 모든 import는 최상위 import 머리말에 모아 둡니다. 이 머리말은 다른 항목보다 앞에 옵니다.
  • 의존 모듈은 자신을 가져온 모듈보다 먼저 한 번 초기화됩니다. 가져오는 모듈에는 선언과 바인딩을 둘 수 있습니다. 최상위 루프나 대입처럼 자유롭게 실행되는 문장은 둘 수 없습니다.
  • topaz.toml은 도구 모음에서 패키지 정체성, 진입점, 의존성, 빌드 대상을 선택합니다. 별도의 모듈 선언 문법을 추가하지 않습니다.

위 예제처럼 패키지 명령이 진입점과 루트를 정해 줄 수 있습니다. 파일을 직접 지정하면 진입 파일이 있는 디렉터리가 기본 루트가 됩니다. --root로 그 파일을 품은 루트를 직접 지정할 수도 있습니다.

순환 import는 거부됩니다

a.tpzb.tpz를 가져온다고 가정해 봅시다.

import b

export function fromA() -> int {
    1
}

그리고 b.tpz가 다시 a.tpz를 가져옵니다.

import a

export function fromB() -> int {
    2
}

topaz check a.tpz --root .을 실행하면 검사기가 순환을 보고합니다.

출력
error[TPZ3006]: import cycle: a -> b -> a
 --> a.tpz:1:8
  |
1 | import b
  |        ^

a.tpz: 1 diagnostic

가져온 이름을 타입으로만 쓰더라도 모든 import 관계가 순환 검사에 들어갑니다. 순환을 끊으려면 공통 선언을 순환하지 않는 세 번째 모듈로 옮기세요. 의존 방향을 한쪽으로만 정하는 방법도 있습니다. 타입 전용 순환 예외는 없습니다.

정확한 경계

현재 모듈 문법에는 use, 문자열·템플릿 경로, 별칭과 선택 목록의 결합, 공개 목록, 와일드카드 공개, 재공개, 공개 지점 이름 변경, 부수 효과만을 위한 import가 없습니다. export let mut도 거부됩니다. import는 루트 밖으로 나갈 수 없습니다. 이름공간도 비공개 멤버를 드러내지 않습니다.

완전히 테스트한 패키지는 첫 애플리케이션에서 볼 수 있습니다. 매니페스트와 잠금은 패키지와 배포에서 다룹니다. 토파즈가 일부러 받지 않는 익숙한 모듈 표기는 금지·유예 형식에서 이어서 확인하세요.