프로그램 구조

모듈과 가시성

이름이 있는 토파즈 파일을 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는 루트 밖으로 나갈 수 없고 이름공간은 비공개 멤버를 노출하지 않습니다.

완전히 테스트한 패키지는 첫 애플리케이션, 매니페스트와 잠금은 패키지와 배포, 토파즈가 의도적으로 받지 않는 익숙한 모듈 표기는 금지·유예 형식에서 이어서 확인하세요.